Merge remote-tracking branch 'origin/dev' into FileSizeFix
This commit is contained in:
commit
e2345dbe1d
118 changed files with 7082 additions and 5906 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()
|
||||||
21
src/backend/langflow/template/frontend_node/utilities.py
Normal file
21
src/backend/langflow/template/frontend_node/utilities.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
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 = 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.
|
||||||
|
|
|
||||||
2
src/frontend/package-lock.json
generated
2
src/frontend/package-lock.json
generated
|
|
@ -7370,4 +7370,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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": [
|
||||||
|
|
|
||||||
|
|
@ -17,191 +17,191 @@ import IntComponent from "../../../../components/intComponent";
|
||||||
import PromptAreaComponent from "../../../../components/promptComponent";
|
import PromptAreaComponent from "../../../../components/promptComponent";
|
||||||
|
|
||||||
export default function ParameterComponent({
|
export default function ParameterComponent({
|
||||||
left,
|
left,
|
||||||
id,
|
id,
|
||||||
data,
|
data,
|
||||||
tooltipTitle,
|
tooltipTitle,
|
||||||
title,
|
title,
|
||||||
color,
|
color,
|
||||||
type,
|
type,
|
||||||
name = "",
|
name = "",
|
||||||
required = false,
|
required = false,
|
||||||
}: ParameterComponentType) {
|
}: ParameterComponentType) {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
const updateNodeInternals = useUpdateNodeInternals();
|
||||||
const [position, setPosition] = useState(0);
|
const [position, setPosition] = useState(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
|
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
|
||||||
setPosition(ref.current.offsetTop + ref.current.clientHeight / 2);
|
setPosition(ref.current.offsetTop + ref.current.clientHeight / 2);
|
||||||
updateNodeInternals(data.id);
|
updateNodeInternals(data.id);
|
||||||
}
|
}
|
||||||
}, [data.id, ref, updateNodeInternals]);
|
}, [data.id, ref, updateNodeInternals]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateNodeInternals(data.id);
|
updateNodeInternals(data.id);
|
||||||
}, [data.id, position, updateNodeInternals]);
|
}, [data.id, position, updateNodeInternals]);
|
||||||
|
|
||||||
const [enabled, setEnabled] = useState(
|
const [enabled, setEnabled] = useState(
|
||||||
data.node.template[name]?.value ?? false
|
data.node.template[name]?.value ?? false
|
||||||
);
|
);
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
const { reactFlowInstance } = useContext(typesContext);
|
||||||
let disabled =
|
let disabled =
|
||||||
reactFlowInstance?.getEdges().some((e) => e.targetHandle === id) ?? false;
|
reactFlowInstance?.getEdges().some((e) => e.targetHandle === id) ?? false;
|
||||||
const { save } = useContext(TabsContext);
|
const { save } = useContext(TabsContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="w-full flex flex-wrap justify-between items-center bg-gray-50 dark:bg-gray-800 dark:text-white mt-1 px-5 py-2"
|
className="w-full flex flex-wrap justify-between items-center bg-gray-50 dark:bg-gray-800 dark:text-white mt-1 px-5 py-2"
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<div className={"text-sm truncate w-full " + (left ? "" : "text-end")}>
|
<div className={"text-sm truncate w-full " + (left ? "" : "text-end")}>
|
||||||
{title}
|
{title}
|
||||||
<span className="text-red-600">{required ? " *" : ""}</span>
|
<span className="text-red-600">{required ? " *" : ""}</span>
|
||||||
</div>
|
</div>
|
||||||
{left &&
|
{left &&
|
||||||
(type === "str" ||
|
(type === "str" ||
|
||||||
type === "bool" ||
|
type === "bool" ||
|
||||||
type === "float" ||
|
type === "float" ||
|
||||||
type === "code" ||
|
type === "code" ||
|
||||||
type === "prompt" ||
|
type === "prompt" ||
|
||||||
type === "file" ||
|
type === "file" ||
|
||||||
type === "int") ? (
|
type === "int") ? (
|
||||||
<></>
|
<></>
|
||||||
) : (
|
) : (
|
||||||
<Tooltip title={tooltipTitle + (required ? " (required)" : "")}>
|
<Tooltip title={tooltipTitle + (required ? " (required)" : "")}>
|
||||||
<Handle
|
<Handle
|
||||||
type={left ? "target" : "source"}
|
type={left ? "target" : "source"}
|
||||||
position={left ? Position.Left : Position.Right}
|
position={left ? Position.Left : Position.Right}
|
||||||
id={id}
|
id={id}
|
||||||
isValidConnection={(connection) =>
|
isValidConnection={(connection) =>
|
||||||
isValidConnection(connection, reactFlowInstance)
|
isValidConnection(connection, reactFlowInstance)
|
||||||
}
|
}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
left ? "-ml-0.5 " : "-mr-0.5 ",
|
left ? "-ml-0.5 " : "-mr-0.5 ",
|
||||||
"w-3 h-3 rounded-full border-2 bg-white dark:bg-gray-800"
|
"w-3 h-3 rounded-full border-2 bg-white dark:bg-gray-800"
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
borderColor: color,
|
borderColor: color,
|
||||||
top: position,
|
top: position,
|
||||||
}}
|
}}
|
||||||
></Handle>
|
></Handle>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{left === true &&
|
{left === true &&
|
||||||
type === "str" &&
|
type === "str" &&
|
||||||
!data.node.template[name].options ? (
|
!data.node.template[name].options ? (
|
||||||
<div className="mt-2 w-full">
|
<div className="mt-2 w-full">
|
||||||
{data.node.template[name].list ? (
|
{data.node.template[name].list ? (
|
||||||
<InputListComponent
|
<InputListComponent
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
value={
|
value={
|
||||||
!data.node.template[name].value ||
|
!data.node.template[name].value ||
|
||||||
data.node.template[name].value === ""
|
data.node.template[name].value === ""
|
||||||
? [""]
|
? [""]
|
||||||
: data.node.template[name].value
|
: data.node.template[name].value
|
||||||
}
|
}
|
||||||
onChange={(t: string[]) => {
|
onChange={(t: string[]) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : data.node.template[name].multiline ? (
|
) : data.node.template[name].multiline ? (
|
||||||
<TextAreaComponent
|
<TextAreaComponent
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t: string) => {
|
onChange={(t: string) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<InputComponent
|
<InputComponent
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
password={data.node.template[name].password ?? false}
|
password={data.node.template[name].password ?? false}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t) => {
|
onChange={(t) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : left === true && type === "bool" ? (
|
) : left === true && type === "bool" ? (
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<ToggleComponent
|
<ToggleComponent
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
enabled={enabled}
|
enabled={enabled}
|
||||||
setEnabled={(t) => {
|
setEnabled={(t) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
setEnabled(t);
|
setEnabled(t);
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : left === true && type === "float" ? (
|
) : left === true && type === "float" ? (
|
||||||
<FloatComponent
|
<FloatComponent
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t) => {
|
onChange={(t) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : left === true &&
|
) : left === true &&
|
||||||
type === "str" &&
|
type === "str" &&
|
||||||
data.node.template[name].options ? (
|
data.node.template[name].options ? (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
options={data.node.template[name].options}
|
options={data.node.template[name].options}
|
||||||
onSelect={(newValue) => (data.node.template[name].value = newValue)}
|
onSelect={(newValue) => (data.node.template[name].value = newValue)}
|
||||||
value={data.node.template[name].value ?? "Choose an option"}
|
value={data.node.template[name].value ?? "Choose an option"}
|
||||||
></Dropdown>
|
></Dropdown>
|
||||||
) : left === true && type === "code" ? (
|
) : left === true && type === "code" ? (
|
||||||
<CodeAreaComponent
|
<CodeAreaComponent
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t: string) => {
|
onChange={(t: string) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : left === true && type === "file" ? (
|
) : left === true && type === "file" ? (
|
||||||
<InputFileComponent
|
<InputFileComponent
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t: string) => {
|
onChange={(t: string) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
}}
|
}}
|
||||||
fileTypes={data.node.template[name].fileTypes}
|
fileTypes={data.node.template[name].fileTypes}
|
||||||
suffixes={data.node.template[name].suffixes}
|
suffixes={data.node.template[name].suffixes}
|
||||||
onFileChange={(t: string) => {
|
onFileChange={(t: string) => {
|
||||||
data.node.template[name].content = t;
|
data.node.template[name].content = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
></InputFileComponent>
|
></InputFileComponent>
|
||||||
) : left === true && type === "int" ? (
|
) : left === true && type === "int" ? (
|
||||||
<IntComponent
|
<IntComponent
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t) => {
|
onChange={(t) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : left === true && type === "prompt" ? (
|
) : left === true && type === "prompt" ? (
|
||||||
<PromptAreaComponent
|
<PromptAreaComponent
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t: string) => {
|
onChange={(t: string) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import {
|
import {
|
||||||
XCircleIcon,
|
XCircleIcon,
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
InformationCircleIcon,
|
InformationCircleIcon,
|
||||||
CheckCircleIcon,
|
CheckCircleIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
|
|
@ -10,145 +10,152 @@ import { useState } from "react";
|
||||||
import { SingleAlertComponentType } from "../../../../types/alerts";
|
import { SingleAlertComponentType } from "../../../../types/alerts";
|
||||||
|
|
||||||
export default function SingleAlert({
|
export default function SingleAlert({
|
||||||
dropItem,
|
dropItem,
|
||||||
removeAlert,
|
removeAlert,
|
||||||
}: SingleAlertComponentType) {
|
}: SingleAlertComponentType) {
|
||||||
const [show, setShow] = useState(true);
|
const [show, setShow] = useState(true);
|
||||||
const type = dropItem.type;
|
const type = dropItem.type;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition
|
<Transition
|
||||||
className="relative"
|
className="relative"
|
||||||
show={show}
|
show={show}
|
||||||
appear={true}
|
appear={true}
|
||||||
enter="transition-transform duration-500 ease-out"
|
enter="transition-transform duration-500 ease-out"
|
||||||
enterFrom={"transform translate-x-[-100%]"}
|
enterFrom={"transform translate-x-[-100%]"}
|
||||||
enterTo={"transform translate-x-0"}
|
enterTo={"transform translate-x-0"}
|
||||||
leave="transition-transform duration-500 ease-in"
|
leave="transition-transform duration-500 ease-in"
|
||||||
leaveFrom={"transform translate-x-0"}
|
leaveFrom={"transform translate-x-0"}
|
||||||
leaveTo={"transform translate-x-[-100%]"}
|
leaveTo={"transform translate-x-[-100%]"}
|
||||||
>
|
>
|
||||||
{type === "error" ? (
|
{type === "error" ? (
|
||||||
<div
|
<div
|
||||||
className="flex bg-red-50 dark:bg-red-900 rounded-md p-3 mb-2 mx-2"
|
className="flex bg-red-50 dark:bg-red-900 rounded-md p-3 mb-2 mx-2"
|
||||||
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
|
||||||
</div>
|
className="h-5 w-5 text-red-400 dark:text-red-50"
|
||||||
<div className="ml-3">
|
aria-hidden="true"
|
||||||
<h3 className="text-sm break-words font-medium text-red-800 dark:text-white/80">
|
/>
|
||||||
{dropItem.title}
|
</div>
|
||||||
</h3>
|
<div className="ml-3">
|
||||||
{dropItem.list ? (
|
<h3 className="text-sm break-words font-medium text-red-800 dark:text-white/80">
|
||||||
<div className="mt-2 text-sm text-red-700 dark:text-red-50">
|
{dropItem.title}
|
||||||
<ul className="list-disc space-y-1 pl-5">
|
</h3>
|
||||||
{dropItem.list.map((item, idx) => (
|
{dropItem.list ? (
|
||||||
<li className="break-words" key={idx}>{item}</li>
|
<div className="mt-2 text-sm text-red-700 dark:text-red-50">
|
||||||
))}
|
<ul className="list-disc space-y-1 pl-5">
|
||||||
</ul>
|
{dropItem.list.map((item, idx) => (
|
||||||
</div>
|
<li className="break-words" key={idx}>
|
||||||
) : (
|
{item}
|
||||||
<></>
|
</li>
|
||||||
)}
|
))}
|
||||||
</div>
|
</ul>
|
||||||
<div className="ml-auto pl-3">
|
</div>
|
||||||
<div className="-mx-1.5 -my-1.5">
|
) : (
|
||||||
<button
|
<></>
|
||||||
type="button"
|
)}
|
||||||
onClick={() => {
|
</div>
|
||||||
setShow(false);
|
<div className="ml-auto pl-3">
|
||||||
setTimeout(() => {
|
<div className="-mx-1.5 -my-1.5">
|
||||||
removeAlert(dropItem.id);
|
<button
|
||||||
}, 500);
|
type="button"
|
||||||
}}
|
onClick={() => {
|
||||||
className="inline-flex rounded-md bg-red-50 dark:bg-transparent p-1.5 text-red-500 dark:text-red-50"
|
setShow(false);
|
||||||
>
|
setTimeout(() => {
|
||||||
<span className="sr-only">Dismiss</span>
|
removeAlert(dropItem.id);
|
||||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
}, 500);
|
||||||
</button>
|
}}
|
||||||
</div>
|
className="inline-flex rounded-md bg-red-50 dark:bg-transparent p-1.5 text-red-500 dark:text-red-50"
|
||||||
</div>
|
>
|
||||||
</div>
|
<span className="sr-only">Dismiss</span>
|
||||||
) : type === "notice" ? (
|
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
<div
|
</button>
|
||||||
className="flex rounded-md bg-blue-50 dark:bg-blue-900 p-3 mb-2 mx-2"
|
</div>
|
||||||
key={dropItem.id}
|
</div>
|
||||||
>
|
</div>
|
||||||
<div className="flex-shrink-0">
|
) : type === "notice" ? (
|
||||||
<InformationCircleIcon
|
<div
|
||||||
className="h-5 w-5 text-blue-400 dark:text-blue-50"
|
className="flex rounded-md bg-blue-50 dark:bg-blue-900 p-3 mb-2 mx-2"
|
||||||
aria-hidden="true"
|
key={dropItem.id}
|
||||||
/>
|
>
|
||||||
</div>
|
<div className="flex-shrink-0">
|
||||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
<InformationCircleIcon
|
||||||
<p className="text-sm text-blue-700 dark:text-white/80">{dropItem.title}</p>
|
className="h-5 w-5 text-blue-400 dark:text-blue-50"
|
||||||
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
aria-hidden="true"
|
||||||
{dropItem.link ? (
|
/>
|
||||||
<Link
|
</div>
|
||||||
to={dropItem.link}
|
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||||
className="whitespace-nowrap font-medium text-blue-700 dark:text-blue-50 dark:hover:text-blue-100 hover:text-blue-600"
|
<p className="text-sm text-blue-700 dark:text-white/80">
|
||||||
>
|
{dropItem.title}
|
||||||
Details
|
</p>
|
||||||
</Link>
|
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
||||||
) : (
|
{dropItem.link ? (
|
||||||
<></>
|
<Link
|
||||||
)}
|
to={dropItem.link}
|
||||||
</p>
|
className="whitespace-nowrap font-medium text-blue-700 dark:text-blue-50 dark:hover:text-blue-100 hover:text-blue-600"
|
||||||
</div>
|
>
|
||||||
<div className="ml-auto pl-3">
|
Details
|
||||||
<div className="-mx-1.5 -my-1.5">
|
</Link>
|
||||||
<button
|
) : (
|
||||||
type="button"
|
<></>
|
||||||
onClick={() => {
|
)}
|
||||||
setShow(false);
|
</p>
|
||||||
setTimeout(() => {
|
</div>
|
||||||
removeAlert(dropItem.id);
|
<div className="ml-auto pl-3">
|
||||||
}, 500);
|
<div className="-mx-1.5 -my-1.5">
|
||||||
}}
|
<button
|
||||||
className="inline-flex rounded-md bg-blue-50 dark:bg-transparent p-1.5 text-blue-500 dark:text-blue-50"
|
type="button"
|
||||||
>
|
onClick={() => {
|
||||||
<span className="sr-only">Dismiss</span>
|
setShow(false);
|
||||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
setTimeout(() => {
|
||||||
</button>
|
removeAlert(dropItem.id);
|
||||||
</div>
|
}, 500);
|
||||||
</div>
|
}}
|
||||||
</div>
|
className="inline-flex rounded-md bg-blue-50 dark:bg-transparent p-1.5 text-blue-500 dark:text-blue-50"
|
||||||
) : (
|
>
|
||||||
<div
|
<span className="sr-only">Dismiss</span>
|
||||||
className="flex bg-green-50 dark:bg-green-900 p-3 mb-2 mx-2 rounded-md"
|
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
key={dropItem.id}
|
</button>
|
||||||
>
|
</div>
|
||||||
<div className="flex-shrink-0">
|
</div>
|
||||||
<CheckCircleIcon
|
</div>
|
||||||
className="h-5 w-5 text-green-400 dark:text-green-50"
|
) : (
|
||||||
aria-hidden="true"
|
<div
|
||||||
/>
|
className="flex bg-green-50 dark:bg-green-900 p-3 mb-2 mx-2 rounded-md"
|
||||||
</div>
|
key={dropItem.id}
|
||||||
<div className="ml-3">
|
>
|
||||||
<p className="text-sm font-medium text-green-800 dark:bg-white/80">
|
<div className="flex-shrink-0">
|
||||||
{dropItem.title}
|
<CheckCircleIcon
|
||||||
</p>
|
className="h-5 w-5 text-green-400 dark:text-green-50"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
<div className="ml-auto pl-3">
|
/>
|
||||||
<div className="-mx-1.5 -my-1.5">
|
</div>
|
||||||
<button
|
<div className="ml-3">
|
||||||
type="button"
|
<p className="text-sm font-medium text-green-800 dark:bg-white/80">
|
||||||
onClick={() => {
|
{dropItem.title}
|
||||||
setShow(false);
|
</p>
|
||||||
setTimeout(() => {
|
</div>
|
||||||
removeAlert(dropItem.id);
|
<div className="ml-auto pl-3">
|
||||||
}, 500);
|
<div className="-mx-1.5 -my-1.5">
|
||||||
}}
|
<button
|
||||||
className="inline-flex rounded-md bg-green-50 dark:bg-transparent p-1.5 text-green-500 dark:text-green-50"
|
type="button"
|
||||||
>
|
onClick={() => {
|
||||||
<span className="sr-only">Dismiss</span>
|
setShow(false);
|
||||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
setTimeout(() => {
|
||||||
</button>
|
removeAlert(dropItem.id);
|
||||||
</div>
|
}, 500);
|
||||||
</div>
|
}}
|
||||||
</div>
|
className="inline-flex rounded-md bg-green-50 dark:bg-transparent p-1.5 text-green-500 dark:text-green-50"
|
||||||
)}
|
>
|
||||||
</Transition>
|
<span className="sr-only">Dismiss</span>
|
||||||
);
|
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Transition>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,60 +7,60 @@ import { AlertDropdownType } from "../../types/alerts";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import { useOnClickOutside } from "../hooks/useOnClickOutside";
|
import { useOnClickOutside } from "../hooks/useOnClickOutside";
|
||||||
export default function AlertDropdown({}: AlertDropdownType) {
|
export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const componentRef = useRef<HTMLDivElement>(null);
|
const componentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Use the custom hook
|
// Use the custom hook
|
||||||
useOnClickOutside(componentRef, () => {
|
useOnClickOutside(componentRef, () => {
|
||||||
closePopUp();
|
closePopUp();
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
notificationList,
|
notificationList,
|
||||||
clearNotificationList,
|
clearNotificationList,
|
||||||
removeFromNotificationList,
|
removeFromNotificationList,
|
||||||
} = useContext(alertContext);
|
} = useContext(alertContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={componentRef}
|
ref={componentRef}
|
||||||
className="z-10 py-3 pb-4 px-2 rounded-md bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[400px] h-[500px] flex flex-col"
|
className="z-10 py-3 pb-4 px-2 rounded-md bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[400px] h-[500px] flex flex-col"
|
||||||
>
|
>
|
||||||
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800 dark:text-gray-200">
|
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800 dark:text-gray-200">
|
||||||
Notifications
|
Notifications
|
||||||
<div className="flex gap-3 pr-3 ">
|
<div className="flex gap-3 pr-3 ">
|
||||||
<button
|
<button
|
||||||
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
closePopUp();
|
closePopUp();
|
||||||
setTimeout(clearNotificationList, 100);
|
setTimeout(clearNotificationList, 100);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
|
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
||||||
onClick={closePopUp}
|
onClick={closePopUp}
|
||||||
>
|
>
|
||||||
<XMarkIcon className="h-5 w-5" />
|
<XMarkIcon className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide text-gray-900 dark:text-gray-300">
|
<div className="mt-3 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide text-gray-900 dark:text-gray-300">
|
||||||
{notificationList.length !== 0 ? (
|
{notificationList.length !== 0 ? (
|
||||||
notificationList.map((alertItem, index) => (
|
notificationList.map((alertItem, index) => (
|
||||||
<SingleAlert
|
<SingleAlert
|
||||||
key={alertItem.id}
|
key={alertItem.id}
|
||||||
dropItem={alertItem}
|
dropItem={alertItem}
|
||||||
removeAlert={removeFromNotificationList}
|
removeAlert={removeFromNotificationList}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full w-full pb-16 text-gray-500 dark:text-gray-500 flex justify-center items-center">
|
<div className="h-full w-full pb-16 text-gray-500 dark:text-gray-500 flex justify-center items-center">
|
||||||
No new notifications
|
No new notifications
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,63 +4,68 @@ import { useEffect, useState } from "react";
|
||||||
import { ErrorAlertType } from "../../types/alerts";
|
import { ErrorAlertType } from "../../types/alerts";
|
||||||
|
|
||||||
export default function ErrorAlert({
|
export default function ErrorAlert({
|
||||||
title,
|
title,
|
||||||
list = [],
|
list = [],
|
||||||
id,
|
id,
|
||||||
removeAlert,
|
removeAlert,
|
||||||
}: ErrorAlertType) {
|
}: ErrorAlertType) {
|
||||||
const [show, setShow] = useState(true);
|
const [show, setShow] = useState(true);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) {
|
if (show) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
removeAlert(id);
|
removeAlert(id);
|
||||||
}, 500);
|
}, 500);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
}, [id, removeAlert, show]);
|
}, [id, removeAlert, show]);
|
||||||
return (
|
return (
|
||||||
<Transition
|
<Transition
|
||||||
className="relative"
|
className="relative"
|
||||||
show={show}
|
show={show}
|
||||||
appear={true}
|
appear={true}
|
||||||
enter="transition-transform duration-500 ease-out"
|
enter="transition-transform duration-500 ease-out"
|
||||||
enterFrom={"transform translate-x-[-100%]"}
|
enterFrom={"transform translate-x-[-100%]"}
|
||||||
enterTo={"transform translate-x-0"}
|
enterTo={"transform translate-x-0"}
|
||||||
leave="transition-transform duration-500 ease-in"
|
leave="transition-transform duration-500 ease-in"
|
||||||
leaveFrom={"transform translate-x-0"}
|
leaveFrom={"transform translate-x-0"}
|
||||||
leaveTo={"transform translate-x-[-100%]"}
|
leaveTo={"transform translate-x-[-100%]"}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
removeAlert(id);
|
removeAlert(id);
|
||||||
}, 500);
|
}, 500);
|
||||||
}}
|
}}
|
||||||
className="rounded-md w-96 mt-6 shadow-xl bg-red-50 dark:bg-red-900 p-4 cursor-pointer"
|
className="rounded-md w-96 mt-6 shadow-xl bg-red-50 dark:bg-red-900 p-4 cursor-pointer"
|
||||||
>
|
>
|
||||||
<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
|
||||||
</div>
|
className="h-5 w-5 text-red-400 dark:text-red-50"
|
||||||
<div className="ml-3">
|
aria-hidden="true"
|
||||||
<h3 className="text-sm font-medium text-red-800 dark:text-white/80">{title}</h3>
|
/>
|
||||||
{list.length !== 0 ? (
|
</div>
|
||||||
<div className="mt-2 text-sm text-red-700 dark:text-red-50">
|
<div className="ml-3">
|
||||||
<ul className="list-disc space-y-1 pl-5">
|
<h3 className="text-sm font-medium text-red-800 dark:text-white/80">
|
||||||
{list.map((item, index) => (
|
{title}
|
||||||
<li key={index}>{item}</li>
|
</h3>
|
||||||
))}
|
{list.length !== 0 ? (
|
||||||
</ul>
|
<div className="mt-2 text-sm text-red-700 dark:text-red-50">
|
||||||
</div>
|
<ul className="list-disc space-y-1 pl-5">
|
||||||
) : (
|
{list.map((item, index) => (
|
||||||
<></>
|
<li key={index}>{item}</li>
|
||||||
)}
|
))}
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
</Transition>
|
<></>
|
||||||
);
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,33 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
export function useOnClickOutside(ref, handler) {
|
export function useOnClickOutside(ref, handler) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = (event) => {
|
const listener = (event) => {
|
||||||
// Do nothing if clicking ref's element or its children
|
// Do nothing if clicking ref's element or its children
|
||||||
if (!ref.current || ref.current.contains(event.target)) {
|
if (!ref.current || ref.current.contains(event.target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
handler(event);
|
handler(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Attach the listener to the document
|
// Attach the listener to the document
|
||||||
document.addEventListener("mousedown", listener, { passive: true });
|
document.addEventListener("mousedown", listener, { passive: true });
|
||||||
|
|
||||||
// Attach the listener to the react-flow instance
|
// Attach the listener to the react-flow instance
|
||||||
const reactFlowContainer = document.querySelector(".react-flow");
|
const reactFlowContainer = document.querySelector(".react-flow");
|
||||||
if (reactFlowContainer) {
|
if (reactFlowContainer) {
|
||||||
reactFlowContainer.addEventListener("mousedown", listener, {
|
reactFlowContainer.addEventListener("mousedown", listener, {
|
||||||
passive: true,
|
passive: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up the listener when the component is unmounted
|
// Clean up the listener when the component is unmounted
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("mousedown", listener);
|
document.removeEventListener("mousedown", listener);
|
||||||
if (reactFlowContainer) {
|
if (reactFlowContainer) {
|
||||||
reactFlowContainer.removeEventListener("mousedown", listener);
|
reactFlowContainer.removeEventListener("mousedown", listener);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [ref, handler]); // Rerun only if ref or handler changes
|
}, [ref, handler]); // Rerun only if ref or handler changes
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,63 +5,63 @@ import { Link } from "react-router-dom";
|
||||||
import { NoticeAlertType } from "../../types/alerts";
|
import { NoticeAlertType } from "../../types/alerts";
|
||||||
|
|
||||||
export default function NoticeAlert({
|
export default function NoticeAlert({
|
||||||
title,
|
title,
|
||||||
link = "",
|
link = "",
|
||||||
id,
|
id,
|
||||||
removeAlert,
|
removeAlert,
|
||||||
}: NoticeAlertType) {
|
}: NoticeAlertType) {
|
||||||
const [show, setShow] = useState(true);
|
const [show, setShow] = useState(true);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) {
|
if (show) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
removeAlert(id);
|
removeAlert(id);
|
||||||
}, 500);
|
}, 500);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
}, [id, removeAlert, show]);
|
}, [id, removeAlert, show]);
|
||||||
return (
|
return (
|
||||||
<Transition
|
<Transition
|
||||||
show={show}
|
show={show}
|
||||||
enter="transition-transform duration-500 ease-out"
|
enter="transition-transform duration-500 ease-out"
|
||||||
enterFrom={"transform translate-x-[-100%]"}
|
enterFrom={"transform translate-x-[-100%]"}
|
||||||
enterTo={"transform translate-x-0"}
|
enterTo={"transform translate-x-0"}
|
||||||
leave="transition-transform duration-500 ease-in"
|
leave="transition-transform duration-500 ease-in"
|
||||||
leaveFrom={"transform translate-x-0"}
|
leaveFrom={"transform translate-x-0"}
|
||||||
leaveTo={"transform translate-x-[-100%]"}
|
leaveTo={"transform translate-x-[-100%]"}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
removeAlert(id);
|
removeAlert(id);
|
||||||
}}
|
}}
|
||||||
className="rounded-md w-96 mt-6 shadow-xl bg-blue-50 dark:bg-blue-900 p-4"
|
className="rounded-md w-96 mt-6 shadow-xl bg-blue-50 dark:bg-blue-900 p-4"
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<InformationCircleIcon
|
<InformationCircleIcon
|
||||||
className="h-5 w-5 text-blue-400 dark:text-blue-50"
|
className="h-5 w-5 text-blue-400 dark:text-blue-50"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||||
<p className="text-sm text-blue-700 dark:text-white/80">{title}</p>
|
<p className="text-sm text-blue-700 dark:text-white/80">{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">
|
||||||
{link !== "" ? (
|
{link !== "" ? (
|
||||||
<Link
|
<Link
|
||||||
to={link}
|
to={link}
|
||||||
className="whitespace-nowrap font-medium text-blue-700 dark:text-blue-50 hover:dark:text-blue-10 hover:text-blue-600"
|
className="whitespace-nowrap font-medium text-blue-700 dark:text-blue-50 hover:dark:text-blue-10 hover:text-blue-600"
|
||||||
>
|
>
|
||||||
Details
|
Details
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,50 +4,52 @@ import { useEffect, useState } from "react";
|
||||||
import { SuccessAlertType } from "../../types/alerts";
|
import { SuccessAlertType } from "../../types/alerts";
|
||||||
|
|
||||||
export default function SuccessAlert({
|
export default function SuccessAlert({
|
||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
removeAlert,
|
removeAlert,
|
||||||
}: SuccessAlertType) {
|
}: SuccessAlertType) {
|
||||||
const [show, setShow] = useState(true);
|
const [show, setShow] = useState(true);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) {
|
if (show) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
removeAlert(id);
|
removeAlert(id);
|
||||||
}, 500);
|
}, 500);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
}, [id, removeAlert, show]);
|
}, [id, removeAlert, show]);
|
||||||
return (
|
return (
|
||||||
<Transition
|
<Transition
|
||||||
show={show}
|
show={show}
|
||||||
enter="transition-transform duration-500 ease-out"
|
enter="transition-transform duration-500 ease-out"
|
||||||
enterFrom={"transform translate-x-[-100%]"}
|
enterFrom={"transform translate-x-[-100%]"}
|
||||||
enterTo={"transform translate-x-0"}
|
enterTo={"transform translate-x-0"}
|
||||||
leave="transition-transform duration-500 ease-in"
|
leave="transition-transform duration-500 ease-in"
|
||||||
leaveFrom={"transform translate-x-0"}
|
leaveFrom={"transform translate-x-0"}
|
||||||
leaveTo={"transform translate-x-[-100%]"}
|
leaveTo={"transform translate-x-[-100%]"}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
removeAlert(id);
|
removeAlert(id);
|
||||||
}}
|
}}
|
||||||
className="rounded-md w-96 mt-6 shadow-xl bg-green-50 dark:bg-green-900 p-4"
|
className="rounded-md w-96 mt-6 shadow-xl bg-green-50 dark:bg-green-900 p-4"
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<CheckCircleIcon
|
<CheckCircleIcon
|
||||||
className="h-5 w-5 text-green-400 dark:text-green-50"
|
className="h-5 w-5 text-green-400 dark:text-green-50"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</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">
|
||||||
</div>
|
{title}
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
</Transition>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,33 @@
|
||||||
export default function CrashErrorComponent({ error, resetErrorBoundary }) {
|
export default function CrashErrorComponent({ error, resetErrorBoundary }) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed top-0 left-0 w-full h-full flex items-center justify-center bg-gray-800 bg-opacity-50 z-50">
|
<div className="fixed top-0 left-0 w-full h-full flex items-center justify-center bg-gray-800 bg-opacity-50 z-50">
|
||||||
<div className="bg-white max-w-4xl h-1/3 min-h-fit rounded-lg shadow-lg p-8 text-start flex flex-col justify-evenly">
|
<div className="bg-white max-w-4xl h-1/3 min-h-fit rounded-lg shadow-lg p-8 text-start flex flex-col justify-evenly">
|
||||||
<h1 className="text-red-500 text-3xl mb-4">
|
<h1 className="text-red-500 text-3xl mb-4">
|
||||||
Oops! An unknown error has occurred.
|
Oops! An unknown error has occurred.
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-700 mb-4 text-xl">
|
<p className="text-gray-700 mb-4 text-xl">
|
||||||
Please click the 'Reset Application' button to restore the
|
Please click the 'Reset Application' button to restore the
|
||||||
application's state. If the error persists, please create an issue on
|
application's state. If the error persists, please create an issue on
|
||||||
our GitHub page. We apologize for any inconvenience this may have
|
our GitHub page. We apologize for any inconvenience this may have
|
||||||
caused.
|
caused.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<button
|
<button
|
||||||
onClick={resetErrorBoundary}
|
onClick={resetErrorBoundary}
|
||||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-4"
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-4"
|
||||||
>
|
>
|
||||||
Reset Application
|
Reset Application
|
||||||
</button>
|
</button>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/logspace-ai/langflow/issues/new"
|
href="https://github.com/logspace-ai/langflow/issues/new"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
|
className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
|
||||||
>
|
>
|
||||||
Create Issue
|
Create Issue
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,119 +6,119 @@ import { classNames } from "../../utils";
|
||||||
import { locationContext } from "../../contexts/locationContext";
|
import { locationContext } from "../../contexts/locationContext";
|
||||||
|
|
||||||
export default function ExtraSidebar() {
|
export default function ExtraSidebar() {
|
||||||
const {
|
const {
|
||||||
current,
|
current,
|
||||||
isStackedOpen,
|
isStackedOpen,
|
||||||
setIsStackedOpen,
|
setIsStackedOpen,
|
||||||
extraNavigation,
|
extraNavigation,
|
||||||
extraComponent,
|
extraComponent,
|
||||||
} = useContext(locationContext);
|
} = useContext(locationContext);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<aside
|
<aside
|
||||||
className={` ${
|
className={` ${
|
||||||
isStackedOpen ? "w-52" : "w-0 "
|
isStackedOpen ? "w-52" : "w-0 "
|
||||||
} flex-shrink-0 flex overflow-hidden flex-col border-r dark:border-r-gray-700 transition-all duration-500`}
|
} flex-shrink-0 flex overflow-hidden flex-col border-r dark:border-r-gray-700 transition-all duration-500`}
|
||||||
>
|
>
|
||||||
<div className="w-52 dark:bg-gray-800 border dark:border-gray-700 overflow-y-auto scrollbar-hide h-full flex flex-col items-start">
|
<div className="w-52 dark:bg-gray-800 border dark:border-gray-700 overflow-y-auto scrollbar-hide h-full flex flex-col items-start">
|
||||||
<div className="flex pt-1 px-4 justify-between align-middle w-full">
|
<div className="flex pt-1 px-4 justify-between align-middle w-full">
|
||||||
<span className="text-gray-900 dark:text-white py-[2px] font-medium ">
|
<span className="text-gray-900 dark:text-white py-[2px] font-medium ">
|
||||||
{extraNavigation.title}
|
{extraNavigation.title}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow flex-col w-full">
|
<div className="flex flex-grow flex-col w-full">
|
||||||
{extraNavigation.options ? (
|
{extraNavigation.options ? (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<nav className="flex-1 space-y-1">
|
<nav className="flex-1 space-y-1">
|
||||||
{extraNavigation.options.map((item) =>
|
{extraNavigation.options.map((item) =>
|
||||||
!item.children ? (
|
!item.children ? (
|
||||||
<div key={item.name}>
|
<div key={item.name}>
|
||||||
<Link
|
<Link
|
||||||
to={item.href}
|
to={item.href}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
item.href.split("/")[2] === current[4]
|
item.href.split("/")[2] === current[4]
|
||||||
? "bg-gray-100 text-gray-900"
|
? "bg-gray-100 text-gray-900"
|
||||||
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
||||||
"group w-full flex items-center pl-2 py-2 text-sm font-medium rounded-md"
|
"group w-full flex items-center pl-2 py-2 text-sm font-medium rounded-md"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<item.icon
|
<item.icon
|
||||||
className={classNames(
|
className={classNames(
|
||||||
item.href.split("/")[2] === current[4]
|
item.href.split("/")[2] === current[4]
|
||||||
? "text-gray-500"
|
? "text-gray-500"
|
||||||
: "text-gray-400 group-hover:text-gray-500",
|
: "text-gray-400 group-hover:text-gray-500",
|
||||||
"mr-3 flex-shrink-0 h-6 w-6"
|
"mr-3 flex-shrink-0 h-6 w-6"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Disclosure
|
<Disclosure
|
||||||
as="div"
|
as="div"
|
||||||
key={item.name}
|
key={item.name}
|
||||||
className="space-y-1"
|
className="space-y-1"
|
||||||
>
|
>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<Disclosure.Button
|
<Disclosure.Button
|
||||||
className={classNames(
|
className={classNames(
|
||||||
item.href.split("/")[2] === current[4]
|
item.href.split("/")[2] === current[4]
|
||||||
? "bg-gray-100 text-gray-900"
|
? "bg-gray-100 text-gray-900"
|
||||||
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
||||||
"group w-full flex items-center pl-2 pr-1 py-2 text-left text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
"group w-full flex items-center pl-2 pr-1 py-2 text-left text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<item.icon
|
<item.icon
|
||||||
className="mr-3 h-6 w-6 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
|
className="mr-3 h-6 w-6 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<span className="flex-1">{item.name}</span>
|
<span className="flex-1">{item.name}</span>
|
||||||
<svg
|
<svg
|
||||||
className={classNames(
|
className={classNames(
|
||||||
open
|
open
|
||||||
? "text-gray-400 rotate-90"
|
? "text-gray-400 rotate-90"
|
||||||
: "text-gray-300",
|
: "text-gray-300",
|
||||||
"ml-3 h-5 w-5 flex-shrink-0 transition-rotate duration-150 ease-in-out group-hover:text-gray-400"
|
"ml-3 h-5 w-5 flex-shrink-0 transition-rotate duration-150 ease-in-out group-hover:text-gray-400"
|
||||||
)}
|
)}
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M6 6L14 10L6 14V6Z"
|
d="M6 6L14 10L6 14V6Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
<Disclosure.Panel className="space-y-1">
|
<Disclosure.Panel className="space-y-1">
|
||||||
{item.children.map((subItem) => (
|
{item.children.map((subItem) => (
|
||||||
<Link
|
<Link
|
||||||
key={subItem.name}
|
key={subItem.name}
|
||||||
to={subItem.href}
|
to={subItem.href}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
subItem.href.split("/")[3] === current[5]
|
subItem.href.split("/")[3] === current[5]
|
||||||
? "bg-gray-100 text-gray-900"
|
? "bg-gray-100 text-gray-900"
|
||||||
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
||||||
"group flex w-full items-center rounded-md py-2 pl-11 pr-2 text-sm font-medium"
|
"group flex w-full items-center rounded-md py-2 pl-11 pr-2 text-sm font-medium"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{subItem.name}
|
{subItem.name}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
extraComponent
|
extraComponent
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,16 @@ import { styled } from "@mui/material/styles";
|
||||||
import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip";
|
import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip";
|
||||||
|
|
||||||
export const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
|
export const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||||
<Tooltip {...props} classes={{ popper: className }} />
|
<Tooltip {...props} classes={{ popper: className }} />
|
||||||
))(({ theme }) => ({
|
))(({ theme }) => ({
|
||||||
[`& .${tooltipClasses.tooltip}`]: {
|
[`& .${tooltipClasses.tooltip}`]: {
|
||||||
backgroundColor: theme.palette.common.white,
|
backgroundColor: theme.palette.common.white,
|
||||||
color: "rgba(0, 0, 0, 0.87)",
|
color: "rgba(0, 0, 0, 0.87)",
|
||||||
boxShadow: theme.shadows[2],
|
boxShadow: theme.shadows[2],
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
[`& .${tooltipClasses.arrow}:before`]: {
|
[`& .${tooltipClasses.arrow}:before`]: {
|
||||||
color: theme.palette.common.white,
|
color: theme.palette.common.white,
|
||||||
boxShadow: theme.shadows[1],
|
boxShadow: theme.shadows[1],
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ import { LightTooltip } from "../LightTooltipComponent";
|
||||||
import { TooltipComponentType } from "../../types/components";
|
import { TooltipComponentType } from "../../types/components";
|
||||||
|
|
||||||
export default function Tooltip({
|
export default function Tooltip({
|
||||||
children,
|
children,
|
||||||
title,
|
title,
|
||||||
placement,
|
placement,
|
||||||
}: TooltipComponentType) {
|
}: TooltipComponentType) {
|
||||||
return (
|
return (
|
||||||
<LightTooltip placement={placement} title={title} arrow>
|
<LightTooltip placement={placement} title={title} arrow>
|
||||||
{children}
|
{children}
|
||||||
</LightTooltip>
|
</LightTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
ChatBubbleLeftEllipsisIcon,
|
ChatBubbleLeftEllipsisIcon,
|
||||||
ChatBubbleOvalLeftEllipsisIcon,
|
ChatBubbleOvalLeftEllipsisIcon,
|
||||||
PlusSmallIcon,
|
PlusSmallIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { ChatMessageType } from "../../../types/chat";
|
import { ChatMessageType } from "../../../types/chat";
|
||||||
|
|
@ -10,49 +10,49 @@ import Convert from "ansi-to-html";
|
||||||
const convert = new Convert({ newline: true });
|
const convert = new Convert({ newline: true });
|
||||||
|
|
||||||
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||||
const [hidden, setHidden] = useState(true);
|
const [hidden, setHidden] = useState(true);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{!chat.isSend ? (
|
{!chat.isSend ? (
|
||||||
<div className="w-full text-start">
|
<div className="w-full text-start">
|
||||||
<div
|
<div
|
||||||
style={{ backgroundColor: nodeColors["chat"] }}
|
style={{ backgroundColor: nodeColors["chat"] }}
|
||||||
className=" relative text-start inline-block text-white rounded-xl overflow-hidden w-fit max-w-[280px] text-sm font-normal rounded-tl-none"
|
className=" relative text-start inline-block text-white rounded-xl overflow-hidden w-fit max-w-[280px] text-sm font-normal rounded-tl-none"
|
||||||
>
|
>
|
||||||
{hidden && chat.thought && chat.thought !== "" && (
|
{hidden && chat.thought && chat.thought !== "" && (
|
||||||
<div
|
<div
|
||||||
onClick={() => setHidden((prev) => !prev)}
|
onClick={() => setHidden((prev) => !prev)}
|
||||||
className="absolute top-2 right-2 cursor-pointer"
|
className="absolute top-2 right-2 cursor-pointer"
|
||||||
>
|
>
|
||||||
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce" />
|
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{chat.thought && chat.thought !== "" && !hidden && (
|
{chat.thought && chat.thought !== "" && !hidden && (
|
||||||
<div
|
<div
|
||||||
onClick={() => setHidden((prev) => !prev)}
|
onClick={() => setHidden((prev) => !prev)}
|
||||||
style={{ backgroundColor: nodeColors["thought"] }}
|
style={{ backgroundColor: nodeColors["thought"] }}
|
||||||
className=" text-start inline-block w-full pb-3 pt-3 px-5 cursor-pointer"
|
className=" text-start inline-block w-full pb-3 pt-3 px-5 cursor-pointer"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: convert.toHtml(chat.thought),
|
__html: convert.toHtml(chat.thought),
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
)}
|
)}
|
||||||
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
||||||
<div
|
<div
|
||||||
className="w-full rounded-b-md px-4 pb-3 pt-3 pr-8"
|
className="w-full rounded-b-md px-4 pb-3 pt-3 pr-8"
|
||||||
style={{ backgroundColor: nodeColors["chat"] }}
|
style={{ backgroundColor: nodeColors["chat"] }}
|
||||||
>
|
>
|
||||||
{chat.message}
|
{chat.message}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full text-end">
|
<div className="w-full text-end">
|
||||||
<div className="text-start inline-block rounded-xl p-3 overflow-hidden w-fit max-w-[280px] px-5 text-sm text-black dark:text-white dark:bg-gray-700 bg-gray-200 font-normal rounded-tr-none">
|
<div className="text-start inline-block rounded-xl p-3 overflow-hidden w-fit max-w-[280px] px-5 text-sm text-black dark:text-white dark:bg-gray-700 bg-gray-200 font-normal rounded-tr-none">
|
||||||
{chat.message}
|
{chat.message}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,26 +7,26 @@ import ChatModal from "../../modals/chatModal";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
export default function Chat({ flow }: ChatType) {
|
export default function Chat({ flow }: ChatType) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (
|
if (
|
||||||
(event.key === "K" || event.key === "k") &&
|
(event.key === "K" || event.key === "k") &&
|
||||||
(event.metaKey || event.ctrlKey)
|
(event.metaKey || event.ctrlKey)
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setOpen((oldState) => !oldState);
|
setOpen((oldState) => !oldState);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener("keydown", handleKeyDown);
|
document.addEventListener("keydown", handleKeyDown);
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("keydown", handleKeyDown);
|
document.removeEventListener("keydown", handleKeyDown);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
|
<ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
|
||||||
<ChatTrigger open={open} setOpen={setOpen} />
|
<ChatTrigger open={open} setOpen={setOpen} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,60 +6,60 @@ import TextAreaModal from "../../modals/textAreaModal";
|
||||||
import { TextAreaComponentType } from "../../types/components";
|
import { TextAreaComponentType } from "../../types/components";
|
||||||
|
|
||||||
export default function CodeAreaComponent({
|
export default function CodeAreaComponent({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
}: TextAreaComponentType) {
|
}: TextAreaComponentType) {
|
||||||
const [myValue, setMyValue] = useState(value);
|
const [myValue, setMyValue] = useState(value);
|
||||||
const { openPopUp } = useContext(PopUpContext);
|
const { openPopUp } = useContext(PopUpContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
|
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="w-full flex items-center gap-3">
|
<div className="w-full flex items-center gap-3">
|
||||||
<span
|
<span
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openPopUp(
|
openPopUp(
|
||||||
<CodeAreaModal
|
<CodeAreaModal
|
||||||
value={myValue}
|
value={myValue}
|
||||||
setValue={(t: string) => {
|
setValue={(t: string) => {
|
||||||
setMyValue(t);
|
setMyValue(t);
|
||||||
onChange(t);
|
onChange(t);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className={
|
className={
|
||||||
"truncate block w-full text-gray-500 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 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
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openPopUp(
|
openPopUp(
|
||||||
<CodeAreaModal
|
<CodeAreaModal
|
||||||
value={myValue}
|
value={myValue}
|
||||||
setValue={(t: string) => {
|
setValue={(t: string) => {
|
||||||
setMyValue(t);
|
setMyValue(t);
|
||||||
onChange(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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,90 +5,90 @@ import { DropDownComponentType } from "../../types/components";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "../../utils";
|
||||||
|
|
||||||
export default function Dropdown({
|
export default function Dropdown({
|
||||||
value,
|
value,
|
||||||
options,
|
options,
|
||||||
onSelect,
|
onSelect,
|
||||||
}: DropDownComponentType) {
|
}: DropDownComponentType) {
|
||||||
let [internalValue, setInternalValue] = useState(
|
let [internalValue, setInternalValue] = useState(
|
||||||
value === "" || !value ? "Choose an option" : value
|
value === "" || !value ? "Choose an option" : value
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Listbox
|
<Listbox
|
||||||
value={internalValue}
|
value={internalValue}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setInternalValue(value);
|
setInternalValue(value);
|
||||||
onSelect(value);
|
onSelect(value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<div className="relative mt-1">
|
<div className="relative mt-1">
|
||||||
<Listbox.Button className="relative w-full cursor-default rounded-md border border-gray-300 bg-white dark:bg-gray-900 py-2 pl-3 pr-10 text-left shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm">
|
<Listbox.Button className="relative w-full cursor-default rounded-md border border-gray-300 bg-white dark:bg-gray-900 py-2 pl-3 pr-10 text-left shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm">
|
||||||
<span className="block w-max truncate">{internalValue}</span>
|
<span className="block w-max truncate">{internalValue}</span>
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
<ChevronUpDownIcon
|
<ChevronUpDownIcon
|
||||||
className="h-5 w-5 text-gray-400"
|
className="h-5 w-5 text-gray-400"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</Listbox.Button>
|
</Listbox.Button>
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
show={open}
|
show={open}
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
leave="transition ease-in duration-100"
|
leave="transition ease-in duration-100"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full dark:bg-gray-800 overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full dark:bg-gray-800 overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||||
{options.map((option, id) => (
|
{options.map((option, id) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={id}
|
key={id}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
classNames(
|
classNames(
|
||||||
active
|
active
|
||||||
? "text-white bg-indigo-600 dark:bg-indigo-500"
|
? "text-white bg-indigo-600 dark:bg-indigo-500"
|
||||||
: "text-gray-900",
|
: "text-gray-900",
|
||||||
"relative cursor-default select-none py-2 pl-3 pr-9 dark:text-gray-300 dark:bg-gray-800"
|
"relative cursor-default select-none py-2 pl-3 pr-9 dark:text-gray-300 dark:bg-gray-800"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
value={option}
|
value={option}
|
||||||
>
|
>
|
||||||
{({ selected, active }) => (
|
{({ selected, active }) => (
|
||||||
<>
|
<>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
selected ? "font-semibold" : "font-normal",
|
selected ? "font-semibold" : "font-normal",
|
||||||
"block truncate"
|
"block truncate"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{option}
|
{option}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{selected ? (
|
{selected ? (
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
active ? "text-white" : "text-indigo-600",
|
active ? "text-white" : "text-indigo-600",
|
||||||
"absolute inset-y-0 right-0 flex items-center pr-4"
|
"absolute inset-y-0 right-0 flex items-center pr-4"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CheckIcon
|
<CheckIcon
|
||||||
className="h-5 w-5"
|
className="h-5 w-5"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Listbox.Option>
|
</Listbox.Option>
|
||||||
))}
|
))}
|
||||||
</Listbox.Options>
|
</Listbox.Options>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,39 +3,32 @@ import { FloatComponentType } from "../../types/components";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
|
|
||||||
export default function FloatComponent({
|
export default function FloatComponent({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
}: FloatComponentType) {
|
}: FloatComponentType) {
|
||||||
const [myValue, setMyValue] = useState(value ?? "");
|
const [myValue, setMyValue] = useState(value ?? "");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
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
|
type="number"
|
||||||
type="number"
|
value={myValue}
|
||||||
value={myValue}
|
className={
|
||||||
className={
|
"block w-full form-input dark:bg-gray-900 arrow-hide 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 form-input dark:bg-gray-900 arrow-hide 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" : "")
|
}
|
||||||
}
|
placeholder="Type a number from zero to one"
|
||||||
placeholder="Type a number from zero to one"
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
setMyValue(e.target.value);
|
||||||
setMyValue(e.target.value);
|
onChange(e.target.value);
|
||||||
onChange(e.target.value);
|
}}
|
||||||
}}
|
/>
|
||||||
onBlur={() => {
|
</div>
|
||||||
setDisableCopyPaste(false)
|
);
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setDisableCopyPaste(true)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,91 +4,84 @@ import { classNames } from "../../utils";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
|
|
||||||
export default function InputComponent({
|
export default function InputComponent({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
password,
|
password,
|
||||||
}: 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("");
|
onChange("");
|
||||||
onChange("");
|
}
|
||||||
}
|
}, [disabled, onChange]);
|
||||||
}, [disabled, onChange]);
|
return (
|
||||||
return (
|
<div
|
||||||
<div
|
className={
|
||||||
className={
|
disabled
|
||||||
disabled
|
? "relative pointer-events-none cursor-not-allowed"
|
||||||
? "relative pointer-events-none cursor-not-allowed"
|
: "relative"
|
||||||
: "relative"
|
}
|
||||||
}
|
>
|
||||||
>
|
<input
|
||||||
<input
|
value={myValue}
|
||||||
value={myValue}
|
className={classNames(
|
||||||
onBlur={() => {
|
"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",
|
||||||
setDisableCopyPaste(false)
|
disabled ? " bg-gray-200 dark:bg-gray-700" : "",
|
||||||
}}
|
password && !pwdVisible && myValue !== "" ? "password" : ""
|
||||||
onFocus={() => {
|
)}
|
||||||
setDisableCopyPaste(true)
|
placeholder="Type a text"
|
||||||
}}
|
onChange={(e) => {
|
||||||
className={classNames(
|
setMyValue(e.target.value);
|
||||||
"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",
|
onChange(e.target.value);
|
||||||
disabled ? " bg-gray-200 dark:bg-gray-700" : "",
|
}}
|
||||||
password && !pwdVisible && myValue !== "" ? "password" : ""
|
/>
|
||||||
)}
|
<button
|
||||||
placeholder="Type a text"
|
className="absolute inset-y-0 right-0 items-center px-4 text-gray-600"
|
||||||
onChange={(e) => {
|
onClick={() => {
|
||||||
setMyValue(e.target.value);
|
setPwdVisible(!pwdVisible);
|
||||||
onChange(e.target.value);
|
}}
|
||||||
}}
|
>
|
||||||
/>
|
{password &&
|
||||||
<button
|
(pwdVisible ? (
|
||||||
className="absolute inset-y-0 right-0 items-center px-4 text-gray-600"
|
<svg
|
||||||
onClick={() => {
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
setPwdVisible(!pwdVisible);
|
fill="none"
|
||||||
}}
|
viewBox="0 0 24 24"
|
||||||
>
|
strokeWidth={1.5}
|
||||||
{password &&
|
stroke="currentColor"
|
||||||
(pwdVisible ? (
|
className="w-5 h-5"
|
||||||
<svg
|
>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<path
|
||||||
fill="none"
|
strokeLinecap="round"
|
||||||
viewBox="0 0 24 24"
|
strokeLinejoin="round"
|
||||||
strokeWidth={1.5}
|
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
|
||||||
stroke="currentColor"
|
/>
|
||||||
className="w-5 h-5"
|
</svg>
|
||||||
>
|
) : (
|
||||||
<path
|
<svg
|
||||||
strokeLinecap="round"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
strokeLinejoin="round"
|
fill="none"
|
||||||
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
|
viewBox="0 0 24 24"
|
||||||
/>
|
strokeWidth={1.5}
|
||||||
</svg>
|
stroke="currentColor"
|
||||||
) : (
|
className="w-5 h-5"
|
||||||
<svg
|
>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<path
|
||||||
fill="none"
|
strokeLinecap="round"
|
||||||
viewBox="0 0 24 24"
|
strokeLinejoin="round"
|
||||||
strokeWidth={1.5}
|
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
|
||||||
stroke="currentColor"
|
/>
|
||||||
className="w-5 h-5"
|
<path
|
||||||
>
|
strokeLinecap="round"
|
||||||
<path
|
strokeLinejoin="round"
|
||||||
strokeLinecap="round"
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
strokeLinejoin="round"
|
/>
|
||||||
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
|
</svg>
|
||||||
/>
|
))}
|
||||||
<path
|
</button>
|
||||||
strokeLinecap="round"
|
</div>
|
||||||
strokeLinejoin="round"
|
);
|
||||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
))}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,83 +4,83 @@ import { alertContext } from "../../contexts/alertContext";
|
||||||
import { FileComponentType } from "../../types/components";
|
import { FileComponentType } from "../../types/components";
|
||||||
|
|
||||||
export default function InputFileComponent({
|
export default function InputFileComponent({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
suffixes,
|
suffixes,
|
||||||
fileTypes,
|
fileTypes,
|
||||||
onFileChange,
|
onFileChange,
|
||||||
}: FileComponentType) {
|
}: FileComponentType) {
|
||||||
const [myValue, setMyValue] = useState(value);
|
const [myValue, setMyValue] = useState(value);
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
onChange("");
|
onChange("");
|
||||||
onFileChange("");
|
onFileChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
|
|
||||||
function attachFile(fileReadEvent: ProgressEvent<FileReader>) {
|
function attachFile(fileReadEvent: ProgressEvent<FileReader>) {
|
||||||
fileReadEvent.preventDefault();
|
fileReadEvent.preventDefault();
|
||||||
const file = fileReadEvent.target.result;
|
const file = fileReadEvent.target.result;
|
||||||
onFileChange(file as string);
|
onFileChange(file as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkFileType(fileName: string): boolean {
|
function checkFileType(fileName: string): boolean {
|
||||||
for (let index = 0; index < suffixes.length; index++) {
|
for (let index = 0; index < suffixes.length; index++) {
|
||||||
if (fileName.endsWith(suffixes[index])) {
|
if (fileName.endsWith(suffixes[index])) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleButtonClick = () => {
|
const handleButtonClick = () => {
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
input.accept = suffixes.join(",");
|
input.accept = suffixes.join(",");
|
||||||
input.style.display = "none";
|
input.style.display = "none";
|
||||||
input.multiple = false;
|
input.multiple = false;
|
||||||
input.onchange = (e: Event) => {
|
input.onchange = (e: Event) => {
|
||||||
const file = (e.target as HTMLInputElement).files?.[0];
|
const file = (e.target as HTMLInputElement).files?.[0];
|
||||||
const fileData = new FileReader();
|
const fileData = new FileReader();
|
||||||
fileData.onload = attachFile;
|
fileData.onload = attachFile;
|
||||||
if (file && checkFileType(file.name)) {
|
if (file && checkFileType(file.name)) {
|
||||||
fileData.readAsDataURL(file);
|
fileData.readAsDataURL(file);
|
||||||
setMyValue(file.name);
|
setMyValue(file.name);
|
||||||
onChange(file.name);
|
onChange(file.name);
|
||||||
} else {
|
} else {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title:
|
title:
|
||||||
"Please select a valid file. Only files this files are allowed:",
|
"Please select a valid file. Only files this files are allowed:",
|
||||||
list: fileTypes,
|
list: fileTypes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
input.click();
|
input.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
|
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="w-full flex items-center gap-3">
|
<div className="w-full flex items-center gap-3">
|
||||||
<span
|
<span
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
className={
|
className={
|
||||||
"truncate block w-full text-gray-500 dark:text-gray-300 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-300 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 : "No file"}
|
{myValue !== "" ? myValue : "No file"}
|
||||||
</span>
|
</span>
|
||||||
<button onClick={handleButtonClick}>
|
<button onClick={handleButtonClick}>
|
||||||
<DocumentMagnifyingGlassIcon className="w-8 h-8 hover:text-blue-600" />
|
<DocumentMagnifyingGlassIcon className="w-8 h-8 hover:text-blue-600" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,79 +6,72 @@ import { TabsContext } from "../../contexts/tabsContext";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
export default function InputListComponent({
|
export default function InputListComponent({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
}: InputListComponentType) {
|
}: InputListComponentType) {
|
||||||
const [inputList, setInputList] = useState(value ?? [""]);
|
const [inputList, setInputList] = useState(value ?? [""]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
setInputList([""]);
|
setInputList([""]);
|
||||||
onChange([""]);
|
onChange([""]);
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
const {setDisableCopyPaste} = useContext(TabsContext)
|
return (
|
||||||
return (
|
<div
|
||||||
<div
|
className={
|
||||||
className={
|
(disabled ? "pointer-events-none cursor-not-allowed" : "") +
|
||||||
(disabled ? "pointer-events-none cursor-not-allowed" : "") +
|
"flex flex-col gap-3"
|
||||||
"flex flex-col gap-3"
|
}
|
||||||
}
|
>
|
||||||
>
|
{inputList.map((i, idx) => (
|
||||||
{inputList.map((i, idx) => (
|
<div key={idx} className="w-full flex gap-3">
|
||||||
<div key={idx} className="w-full flex gap-3">
|
<input
|
||||||
<input
|
type="text"
|
||||||
type="text"
|
value={i}
|
||||||
value={i}
|
className={
|
||||||
className={
|
"block w-full form-input rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
|
||||||
"block w-full form-input rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
|
(disabled ? " bg-gray-200" : "")
|
||||||
(disabled ? " bg-gray-200" : "")
|
}
|
||||||
}
|
placeholder="Type a text"
|
||||||
placeholder="Type a text"
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
setInputList((old) => {
|
||||||
setInputList((old) => {
|
let newInputList = _.cloneDeep(old);
|
||||||
let newInputList = _.cloneDeep(old);
|
newInputList[idx] = e.target.value;
|
||||||
newInputList[idx] = e.target.value;
|
return newInputList;
|
||||||
return newInputList;
|
});
|
||||||
});
|
onChange(inputList);
|
||||||
onChange(inputList);
|
}}
|
||||||
}}
|
/>
|
||||||
onBlur={() => {
|
{idx === inputList.length - 1 ? (
|
||||||
setDisableCopyPaste(false)
|
<button
|
||||||
}}
|
onClick={() => {
|
||||||
onFocus={() => {
|
setInputList((old) => {
|
||||||
setDisableCopyPaste(true)
|
let newInputList = _.cloneDeep(old);
|
||||||
}}
|
newInputList.push("");
|
||||||
/>
|
return newInputList;
|
||||||
{idx === inputList.length - 1 ? (
|
});
|
||||||
<button
|
onChange(inputList);
|
||||||
onClick={() => {
|
}}
|
||||||
setInputList((old) => {
|
>
|
||||||
let newInputList = _.cloneDeep(old);
|
<PlusIcon className="w-4 h-4 hover:text-blue-600" />
|
||||||
newInputList.push("");
|
</button>
|
||||||
return newInputList;
|
) : (
|
||||||
});
|
<button
|
||||||
onChange(inputList);
|
onClick={() => {
|
||||||
}}
|
setInputList((old) => {
|
||||||
>
|
let newInputList = _.cloneDeep(old);
|
||||||
<PlusIcon className="w-4 h-4 hover:text-blue-600" />
|
newInputList.splice(idx, 1);
|
||||||
</button>
|
return newInputList;
|
||||||
) : (
|
});
|
||||||
<button
|
onChange(inputList);
|
||||||
onClick={() => {
|
}}
|
||||||
setInputList((old) => {
|
>
|
||||||
let newInputList = _.cloneDeep(old);
|
<XMarkIcon className="w-4 h-4 hover:text-red-600" />
|
||||||
newInputList.splice(idx, 1);
|
</button>
|
||||||
return newInputList;
|
)}
|
||||||
});
|
</div>
|
||||||
onChange(inputList);
|
))}
|
||||||
}}
|
</div>
|
||||||
>
|
);
|
||||||
<XMarkIcon className="w-4 h-4 hover:text-red-600" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,56 +3,48 @@ import { FloatComponentType } from "../../types/components";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
|
|
||||||
export default function IntComponent({
|
export default function IntComponent({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
}: FloatComponentType) {
|
}: FloatComponentType) {
|
||||||
const [myValue, setMyValue] = useState(value ?? "");
|
const [myValue, setMyValue] = useState(value ?? "");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
const {setDisableCopyPaste} =useContext(TabsContext)
|
return (
|
||||||
return (
|
<div
|
||||||
<div
|
className={
|
||||||
className={
|
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
|
||||||
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
|
}
|
||||||
}
|
>
|
||||||
>
|
<input
|
||||||
<input
|
onKeyDown={(event) => {
|
||||||
onKeyDown={(event) => {
|
if (
|
||||||
if (
|
event.key !== "Backspace" &&
|
||||||
event.key !== "Backspace" &&
|
event.key !== "Enter" &&
|
||||||
event.key !== "Enter" &&
|
event.key !== "Delete" &&
|
||||||
event.key !== "Delete" &&
|
event.key !== "ArrowLeft" &&
|
||||||
event.key !== "ArrowLeft" &&
|
event.key !== "ArrowRight" &&
|
||||||
event.key !== "ArrowRight" &&
|
!/^[-]?\d*$/.test(event.key)
|
||||||
!/^[-]?\d*$/.test(event.key)
|
) {
|
||||||
) {
|
event.preventDefault();
|
||||||
event.preventDefault();
|
}
|
||||||
}
|
}}
|
||||||
}}
|
type="number"
|
||||||
type="number"
|
value={myValue}
|
||||||
value={myValue}
|
className={
|
||||||
className={
|
"block w-full form-input dark:bg-gray-900 arrow-hide 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 form-input dark:bg-gray-900 arrow-hide 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" : "")
|
}
|
||||||
}
|
placeholder="Type a integer number"
|
||||||
placeholder="Type a integer number"
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
setMyValue(e.target.value);
|
||||||
setMyValue(e.target.value);
|
onChange(e.target.value);
|
||||||
onChange(e.target.value);
|
}}
|
||||||
}}
|
/>
|
||||||
onBlur={() => {
|
</div>
|
||||||
setDisableCopyPaste(false)
|
);
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setDisableCopyPaste(true)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
type LoadingComponentProps = {
|
type LoadingComponentProps = {
|
||||||
remSize: number;
|
remSize: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LoadingComponent({ remSize }: LoadingComponentProps) {
|
export default function LoadingComponent({ remSize }: LoadingComponentProps) {
|
||||||
return (
|
return (
|
||||||
<div role="status" className="w-min m-auto">
|
<div role="status" className="w-min m-auto">
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={`w-${remSize} h-${remSize} mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600`}
|
className={`w-${remSize} h-${remSize} mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600`}
|
||||||
viewBox="0 0 100 101"
|
viewBox="0 0 100 101"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||||
fill="currentFill"
|
fill="currentFill"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<br></br>
|
<br></br>
|
||||||
<span className="animate-pulse text-blue-600 text-lg">Loading...</span>
|
<span className="animate-pulse text-blue-600 text-lg">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,63 +4,67 @@ 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,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
}: TextAreaComponentType) {
|
}: TextAreaComponentType) {
|
||||||
const [myValue, setMyValue] = useState(value);
|
const [myValue, setMyValue] = useState(value);
|
||||||
const { openPopUp } = useContext(PopUpContext);
|
const { openPopUp } = useContext(PopUpContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
disabled ? "pointer-events-none cursor-not-allowed w-full" : " w-full"
|
disabled ? "pointer-events-none cursor-not-allowed w-full" : " w-full"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="w-full flex items-center gap-3">
|
<div className="w-full flex items-center gap-3">
|
||||||
<span
|
<span
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openPopUp(
|
openPopUp(
|
||||||
<PromptAreaModal
|
<GenericModal
|
||||||
value={myValue}
|
value={myValue}
|
||||||
setValue={(t: string) => {
|
buttonText="Check & Save"
|
||||||
setMyValue(t);
|
modalTitle="Edit Prompt"
|
||||||
onChange(t);
|
setValue={(t: string) => {
|
||||||
}}
|
setMyValue(t);
|
||||||
/>
|
onChange(t);
|
||||||
);
|
}}
|
||||||
}}
|
/>
|
||||||
className={
|
);
|
||||||
"truncate block w-full text-gray-500 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" : "")
|
className={
|
||||||
}
|
"truncate block w-full text-gray-500 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" : "")
|
||||||
{myValue !== "" ? myValue : "Text empty"}
|
}
|
||||||
</span>
|
>
|
||||||
<button
|
{myValue !== "" ? myValue : "Text empty"}
|
||||||
onClick={() => {
|
</span>
|
||||||
openPopUp(
|
<button
|
||||||
<PromptAreaModal
|
onClick={() => {
|
||||||
value={myValue}
|
openPopUp(
|
||||||
setValue={(t: string) => {
|
<GenericModal
|
||||||
setMyValue(t);
|
value={myValue}
|
||||||
onChange(t);
|
buttonText="Check & Save"
|
||||||
}}
|
modalTitle="Edit Prompt"
|
||||||
/>
|
setValue={(t: string) => {
|
||||||
);
|
setMyValue(t);
|
||||||
}}
|
onChange(t);
|
||||||
>
|
}}
|
||||||
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-blue-600 dark:text-gray-300" />
|
/>
|
||||||
</button>
|
);
|
||||||
</div>
|
}}
|
||||||
</div>
|
>
|
||||||
);
|
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-blue-600 dark:text-gray-300" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,16 +20,43 @@ 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
|
||||||
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-blue-600 dark:text-gray-300" />
|
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" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,59 +4,57 @@ import { useEffect } from "react";
|
||||||
import { ToggleComponentType } from "../../types/components";
|
import { ToggleComponentType } from "../../types/components";
|
||||||
|
|
||||||
export default function ToggleComponent({
|
export default function ToggleComponent({
|
||||||
enabled,
|
enabled,
|
||||||
setEnabled,
|
setEnabled,
|
||||||
disabled,
|
disabled,
|
||||||
}: ToggleComponentType) {
|
}: ToggleComponentType) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
}
|
}
|
||||||
}, [disabled, setEnabled]);
|
}, [disabled, setEnabled]);
|
||||||
return (
|
return (
|
||||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||||
<Switch
|
<Switch
|
||||||
checked={enabled}
|
checked={enabled}
|
||||||
onChange={(x: boolean) => {
|
onChange={(x: boolean) => {
|
||||||
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>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
enabled ? "translate-x-5" : "translate-x-0",
|
enabled ? "translate-x-5" : "translate-x-0",
|
||||||
"pointer-events-none relative inline-block h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out",
|
"pointer-events-none relative inline-block h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out",
|
||||||
disabled
|
disabled
|
||||||
? "bg-gray-200 dark:bg-gray-600"
|
? "bg-gray-200 dark:bg-gray-600"
|
||||||
: "bg-white dark:bg-gray-800"
|
: "bg-white dark:bg-gray-800"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
enabled
|
enabled
|
||||||
? "opacity-0 ease-out duration-100"
|
? "opacity-0 ease-out duration-100"
|
||||||
: "opacity-100 ease-in duration-200",
|
: "opacity-100 ease-in duration-200",
|
||||||
"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
|
? "opacity-100 ease-in duration-200"
|
||||||
? "opacity-100 ease-in duration-200"
|
: "opacity-0 ease-out duration-100",
|
||||||
: "opacity-0 ease-out duration-100",
|
"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>
|
</Switch>
|
||||||
</span>
|
</div>
|
||||||
</Switch>
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,161 +5,161 @@ import _ from "lodash";
|
||||||
|
|
||||||
//types for alertContextType
|
//types for alertContextType
|
||||||
type alertContextType = {
|
type alertContextType = {
|
||||||
errorData: { title: string; list?: Array<string> };
|
errorData: { title: string; list?: Array<string> };
|
||||||
setErrorData: (newState: { title: string; list?: Array<string> }) => void;
|
setErrorData: (newState: { title: string; list?: Array<string> }) => void;
|
||||||
errorOpen: boolean;
|
errorOpen: boolean;
|
||||||
setErrorOpen: (newState: boolean) => void;
|
setErrorOpen: (newState: boolean) => void;
|
||||||
noticeData: { title: string; link?: string };
|
noticeData: { title: string; link?: string };
|
||||||
setNoticeData: (newState: { title: string; link?: string }) => void;
|
setNoticeData: (newState: { title: string; link?: string }) => void;
|
||||||
noticeOpen: boolean;
|
noticeOpen: boolean;
|
||||||
setNoticeOpen: (newState: boolean) => void;
|
setNoticeOpen: (newState: boolean) => void;
|
||||||
successData: { title: string };
|
successData: { title: string };
|
||||||
setSuccessData: (newState: { title: string }) => void;
|
setSuccessData: (newState: { title: string }) => void;
|
||||||
successOpen: boolean;
|
successOpen: boolean;
|
||||||
setSuccessOpen: (newState: boolean) => void;
|
setSuccessOpen: (newState: boolean) => void;
|
||||||
notificationCenter: boolean;
|
notificationCenter: boolean;
|
||||||
setNotificationCenter: (newState: boolean) => void;
|
setNotificationCenter: (newState: boolean) => void;
|
||||||
notificationList: Array<AlertItemType>;
|
notificationList: Array<AlertItemType>;
|
||||||
pushNotificationList: (Object: AlertItemType) => void;
|
pushNotificationList: (Object: AlertItemType) => void;
|
||||||
clearNotificationList: () => void;
|
clearNotificationList: () => void;
|
||||||
removeFromNotificationList: (index: string) => void;
|
removeFromNotificationList: (index: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
//initial values to alertContextType
|
//initial values to alertContextType
|
||||||
const initialValue: alertContextType = {
|
const initialValue: alertContextType = {
|
||||||
errorData: { title: "", list: [] },
|
errorData: { title: "", list: [] },
|
||||||
setErrorData: () => {},
|
setErrorData: () => {},
|
||||||
errorOpen: false,
|
errorOpen: false,
|
||||||
setErrorOpen: () => {},
|
setErrorOpen: () => {},
|
||||||
noticeData: { title: "", link: "" },
|
noticeData: { title: "", link: "" },
|
||||||
setNoticeData: () => {},
|
setNoticeData: () => {},
|
||||||
noticeOpen: false,
|
noticeOpen: false,
|
||||||
setNoticeOpen: () => {},
|
setNoticeOpen: () => {},
|
||||||
successData: { title: "" },
|
successData: { title: "" },
|
||||||
setSuccessData: () => {},
|
setSuccessData: () => {},
|
||||||
successOpen: false,
|
successOpen: false,
|
||||||
setSuccessOpen: () => {},
|
setSuccessOpen: () => {},
|
||||||
notificationCenter: false,
|
notificationCenter: false,
|
||||||
setNotificationCenter: () => {},
|
setNotificationCenter: () => {},
|
||||||
notificationList: [],
|
notificationList: [],
|
||||||
pushNotificationList: () => {},
|
pushNotificationList: () => {},
|
||||||
clearNotificationList: () => {},
|
clearNotificationList: () => {},
|
||||||
removeFromNotificationList: () => {},
|
removeFromNotificationList: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const alertContext = createContext<alertContextType>(initialValue);
|
export const alertContext = createContext<alertContextType>(initialValue);
|
||||||
|
|
||||||
export function AlertProvider({ children }: { children: ReactNode }) {
|
export function AlertProvider({ children }: { children: ReactNode }) {
|
||||||
const [errorData, setErrorDataState] = useState<{
|
const [errorData, setErrorDataState] = useState<{
|
||||||
title: string;
|
title: string;
|
||||||
list?: Array<string>;
|
list?: Array<string>;
|
||||||
}>({ title: "", list: [] });
|
}>({ title: "", list: [] });
|
||||||
const [errorOpen, setErrorOpen] = useState(false);
|
const [errorOpen, setErrorOpen] = useState(false);
|
||||||
const [noticeData, setNoticeDataState] = useState<{
|
const [noticeData, setNoticeDataState] = useState<{
|
||||||
title: string;
|
title: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
}>({ title: "", link: "" });
|
}>({ title: "", link: "" });
|
||||||
const [noticeOpen, setNoticeOpen] = useState(false);
|
const [noticeOpen, setNoticeOpen] = useState(false);
|
||||||
const [successData, setSuccessDataState] = useState<{ title: string }>({
|
const [successData, setSuccessDataState] = useState<{ title: string }>({
|
||||||
title: "",
|
title: "",
|
||||||
});
|
});
|
||||||
const [successOpen, setSuccessOpen] = useState(false);
|
const [successOpen, setSuccessOpen] = useState(false);
|
||||||
const [notificationCenter, setNotificationCenter] = useState(false);
|
const [notificationCenter, setNotificationCenter] = useState(false);
|
||||||
const [notificationList, setNotificationList] = useState([]);
|
const [notificationList, setNotificationList] = useState([]);
|
||||||
const pushNotificationList = (notification: AlertItemType) => {
|
const pushNotificationList = (notification: AlertItemType) => {
|
||||||
setNotificationList((old) => {
|
setNotificationList((old) => {
|
||||||
let newNotificationList = _.cloneDeep(old);
|
let newNotificationList = _.cloneDeep(old);
|
||||||
newNotificationList.unshift(notification);
|
newNotificationList.unshift(notification);
|
||||||
return newNotificationList;
|
return newNotificationList;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Sets the error data state, opens the error dialog and pushes the new error notification to the notification list
|
* Sets the error data state, opens the error dialog and pushes the new error notification to the notification list
|
||||||
* @param newState An object containing the new error data, including title and optional list of error messages
|
* @param newState An object containing the new error data, including title and optional list of error messages
|
||||||
*/
|
*/
|
||||||
function setErrorData(newState: { title: string; list?: Array<string> }) {
|
function setErrorData(newState: { title: string; list?: Array<string> }) {
|
||||||
setErrorDataState(newState);
|
setErrorDataState(newState);
|
||||||
setErrorOpen(true);
|
setErrorOpen(true);
|
||||||
if (newState.title) {
|
if (newState.title) {
|
||||||
setNotificationCenter(true);
|
setNotificationCenter(true);
|
||||||
pushNotificationList({
|
pushNotificationList({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: newState.title,
|
title: newState.title,
|
||||||
list: newState.list,
|
list: newState.list,
|
||||||
id: _.uniqueId(),
|
id: _.uniqueId(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sets the state of the notice data and opens the notice modal, also adds a new notice to the notification center if the title is defined.
|
* Sets the state of the notice data and opens the notice modal, also adds a new notice to the notification center if the title is defined.
|
||||||
* @param newState An object containing the title of the notice and optionally a link.
|
* @param newState An object containing the title of the notice and optionally a link.
|
||||||
*/
|
*/
|
||||||
function setNoticeData(newState: { title: string; link?: string }) {
|
function setNoticeData(newState: { title: string; link?: string }) {
|
||||||
setNoticeDataState(newState);
|
setNoticeDataState(newState);
|
||||||
setNoticeOpen(true);
|
setNoticeOpen(true);
|
||||||
if (newState.title) {
|
if (newState.title) {
|
||||||
// Add new notice to notification center
|
// Add new notice to notification center
|
||||||
setNotificationCenter(true);
|
setNotificationCenter(true);
|
||||||
pushNotificationList({
|
pushNotificationList({
|
||||||
type: "notice",
|
type: "notice",
|
||||||
title: newState.title,
|
title: newState.title,
|
||||||
link: newState.link,
|
link: newState.link,
|
||||||
id: _.uniqueId(),
|
id: _.uniqueId(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Update the success data state and show a success alert notification.
|
* Update the success data state and show a success alert notification.
|
||||||
* @param newState - A state object with a "title" property to set in the success data state.
|
* @param newState - A state object with a "title" property to set in the success data state.
|
||||||
*/
|
*/
|
||||||
function setSuccessData(newState: { title: string }) {
|
function setSuccessData(newState: { title: string }) {
|
||||||
setSuccessDataState(newState); // update the success data state with the provided new state
|
setSuccessDataState(newState); // update the success data state with the provided new state
|
||||||
setSuccessOpen(true); // open the success alert
|
setSuccessOpen(true); // open the success alert
|
||||||
|
|
||||||
// If the new state has a "title" property, add a new success notification to the list
|
// If the new state has a "title" property, add a new success notification to the list
|
||||||
if (newState.title) {
|
if (newState.title) {
|
||||||
setNotificationCenter(true); // show the notification center
|
setNotificationCenter(true); // show the notification center
|
||||||
pushNotificationList({
|
pushNotificationList({
|
||||||
// add the new notification to the list
|
// add the new notification to the list
|
||||||
type: "success",
|
type: "success",
|
||||||
title: newState.title,
|
title: newState.title,
|
||||||
id: _.uniqueId(),
|
id: _.uniqueId(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function clearNotificationList() {
|
function clearNotificationList() {
|
||||||
setNotificationList([]);
|
setNotificationList([]);
|
||||||
}
|
}
|
||||||
function removeFromNotificationList(index: string) {
|
function removeFromNotificationList(index: string) {
|
||||||
// set the notification list to a new array that filters out the alert with the matching id
|
// set the notification list to a new array that filters out the alert with the matching id
|
||||||
setNotificationList((prevAlertsList) =>
|
setNotificationList((prevAlertsList) =>
|
||||||
prevAlertsList.filter((alert) => alert.id !== index)
|
prevAlertsList.filter((alert) => alert.id !== index)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<alertContext.Provider
|
<alertContext.Provider
|
||||||
value={{
|
value={{
|
||||||
removeFromNotificationList,
|
removeFromNotificationList,
|
||||||
clearNotificationList,
|
clearNotificationList,
|
||||||
notificationList,
|
notificationList,
|
||||||
pushNotificationList,
|
pushNotificationList,
|
||||||
setNotificationCenter,
|
setNotificationCenter,
|
||||||
notificationCenter,
|
notificationCenter,
|
||||||
errorData,
|
errorData,
|
||||||
setErrorData,
|
setErrorData,
|
||||||
errorOpen,
|
errorOpen,
|
||||||
setErrorOpen,
|
setErrorOpen,
|
||||||
noticeData,
|
noticeData,
|
||||||
setNoticeData,
|
setNoticeData,
|
||||||
noticeOpen,
|
noticeOpen,
|
||||||
setNoticeOpen,
|
setNoticeOpen,
|
||||||
successData,
|
successData,
|
||||||
setSuccessData,
|
setSuccessData,
|
||||||
successOpen,
|
successOpen,
|
||||||
setSuccessOpen,
|
setSuccessOpen,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</alertContext.Provider>
|
</alertContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,34 @@
|
||||||
import { createContext, useEffect, useState } from "react";
|
import { createContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
type darkContextType = {
|
type darkContextType = {
|
||||||
dark: {};
|
dark: {};
|
||||||
setDark: (newState: {}) => void;
|
setDark: (newState: {}) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialValue = {
|
const initialValue = {
|
||||||
dark: {},
|
dark: {},
|
||||||
setDark: () => {},
|
setDark: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const darkContext = createContext<darkContextType>(initialValue);
|
export const darkContext = createContext<darkContextType>(initialValue);
|
||||||
|
|
||||||
export function DarkProvider({ children }) {
|
export function DarkProvider({ children }) {
|
||||||
const [dark, setDark] = useState(false);
|
const [dark, setDark] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dark) {
|
if (dark) {
|
||||||
document.getElementById("body").classList.add("dark");
|
document.getElementById("body").classList.add("dark");
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("body").classList.remove("dark");
|
document.getElementById("body").classList.remove("dark");
|
||||||
}
|
}
|
||||||
}, [dark]);
|
}, [dark]);
|
||||||
return (
|
return (
|
||||||
<darkContext.Provider
|
<darkContext.Provider
|
||||||
value={{
|
value={{
|
||||||
dark,
|
dark,
|
||||||
setDark,
|
setDark,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</darkContext.Provider>
|
</darkContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,20 @@ import { TabsProvider } from "./tabsContext";
|
||||||
import { TypesProvider } from "./typesContext";
|
import { TypesProvider } from "./typesContext";
|
||||||
|
|
||||||
export default function ContextWrapper({ children }: { children: ReactNode }) {
|
export default function ContextWrapper({ children }: { children: ReactNode }) {
|
||||||
//element to wrap all context
|
//element to wrap all context
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DarkProvider>
|
<DarkProvider>
|
||||||
<TypesProvider>
|
<TypesProvider>
|
||||||
<LocationProvider>
|
<LocationProvider>
|
||||||
<AlertProvider>
|
<AlertProvider>
|
||||||
<TabsProvider>
|
<TabsProvider>
|
||||||
<PopUpProvider>{children}</PopUpProvider>
|
<PopUpProvider>{children}</PopUpProvider>
|
||||||
</TabsProvider>
|
</TabsProvider>
|
||||||
</AlertProvider>
|
</AlertProvider>
|
||||||
</LocationProvider>
|
</LocationProvider>
|
||||||
</TypesProvider>
|
</TypesProvider>
|
||||||
</DarkProvider>
|
</DarkProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,78 +2,78 @@ import { createContext, ReactNode, useState } from "react";
|
||||||
|
|
||||||
//types for location context
|
//types for location context
|
||||||
type locationContextType = {
|
type locationContextType = {
|
||||||
current: Array<string>;
|
current: Array<string>;
|
||||||
setCurrent: (newState: Array<string>) => void;
|
setCurrent: (newState: Array<string>) => void;
|
||||||
isStackedOpen: boolean;
|
isStackedOpen: boolean;
|
||||||
setIsStackedOpen: (newState: boolean) => void;
|
setIsStackedOpen: (newState: boolean) => void;
|
||||||
showSideBar: boolean;
|
showSideBar: boolean;
|
||||||
setShowSideBar: (newState: boolean) => void;
|
setShowSideBar: (newState: boolean) => void;
|
||||||
extraNavigation: {
|
extraNavigation: {
|
||||||
title: string;
|
title: string;
|
||||||
options?: Array<{
|
options?: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
href: string;
|
href: string;
|
||||||
icon: any;
|
icon: any;
|
||||||
children?: Array<any>;
|
children?: Array<any>;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
setExtraNavigation: (newState: {
|
setExtraNavigation: (newState: {
|
||||||
title: string;
|
title: string;
|
||||||
options?: Array<{
|
options?: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
href: string;
|
href: string;
|
||||||
icon: any;
|
icon: any;
|
||||||
children?: Array<any>;
|
children?: Array<any>;
|
||||||
}>;
|
}>;
|
||||||
}) => void;
|
}) => void;
|
||||||
extraComponent: any;
|
extraComponent: any;
|
||||||
setExtraComponent: (newState: any) => void;
|
setExtraComponent: (newState: any) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
//initial value for location context
|
//initial value for location context
|
||||||
const initialValue = {
|
const initialValue = {
|
||||||
//actual
|
//actual
|
||||||
current: window.location.pathname.replace(/\/$/g, "").split("/"),
|
current: window.location.pathname.replace(/\/$/g, "").split("/"),
|
||||||
isStackedOpen:
|
isStackedOpen:
|
||||||
window.innerWidth > 1024 && window.location.pathname.split("/")[1]
|
window.innerWidth > 1024 && window.location.pathname.split("/")[1]
|
||||||
? true
|
? true
|
||||||
: false,
|
: false,
|
||||||
setCurrent: () => {},
|
setCurrent: () => {},
|
||||||
setIsStackedOpen: () => {},
|
setIsStackedOpen: () => {},
|
||||||
showSideBar: window.location.pathname.split("/")[1] ? true : false,
|
showSideBar: window.location.pathname.split("/")[1] ? true : false,
|
||||||
setShowSideBar: () => {},
|
setShowSideBar: () => {},
|
||||||
extraNavigation: { title: "" },
|
extraNavigation: { title: "" },
|
||||||
setExtraNavigation: () => {},
|
setExtraNavigation: () => {},
|
||||||
extraComponent: <></>,
|
extraComponent: <></>,
|
||||||
setExtraComponent: () => {},
|
setExtraComponent: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const locationContext = createContext<locationContextType>(initialValue);
|
export const locationContext = createContext<locationContextType>(initialValue);
|
||||||
|
|
||||||
export function LocationProvider({ children }: { children: ReactNode }) {
|
export function LocationProvider({ children }: { children: ReactNode }) {
|
||||||
const [current, setCurrent] = useState(initialValue.current);
|
const [current, setCurrent] = useState(initialValue.current);
|
||||||
const [isStackedOpen, setIsStackedOpen] = useState(
|
const [isStackedOpen, setIsStackedOpen] = useState(
|
||||||
initialValue.isStackedOpen
|
initialValue.isStackedOpen
|
||||||
);
|
);
|
||||||
const [showSideBar, setShowSideBar] = useState(initialValue.showSideBar);
|
const [showSideBar, setShowSideBar] = useState(initialValue.showSideBar);
|
||||||
const [extraNavigation, setExtraNavigation] = useState({ title: "" });
|
const [extraNavigation, setExtraNavigation] = useState({ title: "" });
|
||||||
const [extraComponent, setExtraComponent] = useState(<></>);
|
const [extraComponent, setExtraComponent] = useState(<></>);
|
||||||
return (
|
return (
|
||||||
<locationContext.Provider
|
<locationContext.Provider
|
||||||
value={{
|
value={{
|
||||||
isStackedOpen,
|
isStackedOpen,
|
||||||
setIsStackedOpen,
|
setIsStackedOpen,
|
||||||
current,
|
current,
|
||||||
setCurrent,
|
setCurrent,
|
||||||
showSideBar,
|
showSideBar,
|
||||||
setShowSideBar,
|
setShowSideBar,
|
||||||
extraNavigation,
|
extraNavigation,
|
||||||
setExtraNavigation,
|
setExtraNavigation,
|
||||||
extraComponent,
|
extraComponent,
|
||||||
setExtraComponent,
|
setExtraComponent,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</locationContext.Provider>
|
</locationContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,31 +3,31 @@ import React, { useState } from "react";
|
||||||
|
|
||||||
// context to set JSX element on the DOM
|
// context to set JSX element on the DOM
|
||||||
export const PopUpContext = createContext({
|
export const PopUpContext = createContext({
|
||||||
openPopUp: (popUpElement: JSX.Element) => {},
|
openPopUp: (popUpElement: JSX.Element) => {},
|
||||||
closePopUp: () => {},
|
closePopUp: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
interface PopUpProviderProps {
|
interface PopUpProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PopUpProvider = ({ children }: PopUpProviderProps) => {
|
const PopUpProvider = ({ children }: PopUpProviderProps) => {
|
||||||
const [popUpElements, setPopUpElements] = useState<JSX.Element[]>([]);
|
const [popUpElements, setPopUpElements] = useState<JSX.Element[]>([]);
|
||||||
|
|
||||||
const openPopUp = (element: JSX.Element) => {
|
const openPopUp = (element: JSX.Element) => {
|
||||||
setPopUpElements((prevPopUps) => [element, ...prevPopUps]);
|
setPopUpElements((prevPopUps) => [element, ...prevPopUps]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const closePopUp = () => {
|
const closePopUp = () => {
|
||||||
setPopUpElements((prevPopUps) => prevPopUps.slice(1));
|
setPopUpElements((prevPopUps) => prevPopUps.slice(1));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopUpContext.Provider value={{ openPopUp, closePopUp }}>
|
<PopUpContext.Provider value={{ openPopUp, closePopUp }}>
|
||||||
{children}
|
{children}
|
||||||
{popUpElements[0]}
|
{popUpElements[0]}
|
||||||
</PopUpContext.Provider>
|
</PopUpContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PopUpProvider;
|
export default PopUpProvider;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
useRef,
|
useRef,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
useContext,
|
useContext,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { FlowType, NodeType } from "../types/flow";
|
import { FlowType, NodeType } from "../types/flow";
|
||||||
import { LangFlowState, TabsContextType } from "../types/tabs";
|
import { LangFlowState, TabsContextType } from "../types/tabs";
|
||||||
|
|
@ -17,339 +17,338 @@ import { addEdge } from "reactflow";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
const TabsContextInitialValue: TabsContextType = {
|
const TabsContextInitialValue: TabsContextType = {
|
||||||
save: () => {},
|
save: () => {},
|
||||||
tabIndex: 0,
|
tabIndex: 0,
|
||||||
setTabIndex: (index: number) => {},
|
setTabIndex: (index: number) => {},
|
||||||
flows: [],
|
flows: [],
|
||||||
removeFlow: (id: string) => {},
|
removeFlow: (id: string) => {},
|
||||||
addFlow: (flowData?: any) => {},
|
addFlow: (flowData?: any) => {},
|
||||||
updateFlow: (newFlow: FlowType) => {},
|
updateFlow: (newFlow: FlowType) => {},
|
||||||
incrementNodeId: () => uuidv4(),
|
incrementNodeId: () => uuidv4(),
|
||||||
downloadFlow: (flow: FlowType) => {},
|
downloadFlow: (flow: FlowType) => {},
|
||||||
uploadFlow: () => {},
|
uploadFlow: () => {},
|
||||||
hardReset: () => {},
|
hardReset: () => {},
|
||||||
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>(
|
||||||
TabsContextInitialValue
|
TabsContextInitialValue
|
||||||
);
|
);
|
||||||
|
|
||||||
export function TabsProvider({ children }: { children: ReactNode }) {
|
export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
const { setNoticeData } = useContext(alertContext);
|
const { setNoticeData } = useContext(alertContext);
|
||||||
const [tabIndex, setTabIndex] = useState(0);
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||||
const [id, setId] = useState(uuidv4());
|
const [id, setId] = useState(uuidv4());
|
||||||
const { templates, reactFlowInstance } = useContext(typesContext);
|
const { templates, reactFlowInstance } = useContext(typesContext);
|
||||||
|
|
||||||
const newNodeId = useRef(uuidv4());
|
const newNodeId = useRef(uuidv4());
|
||||||
function incrementNodeId() {
|
function incrementNodeId() {
|
||||||
newNodeId.current = uuidv4();
|
newNodeId.current = uuidv4();
|
||||||
return newNodeId.current;
|
return newNodeId.current;
|
||||||
}
|
}
|
||||||
function save() {
|
function save() {
|
||||||
// added clone deep to avoid mutating the original object
|
// added clone deep to avoid mutating the original object
|
||||||
let Saveflows = _.cloneDeep(flows);
|
let Saveflows = _.cloneDeep(flows);
|
||||||
if (Saveflows.length !== 0){
|
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) => {
|
||||||
//looking for file fields to prevent saving the content and breaking the flow for exceeding the the data limite for local storage
|
console.log(node.data.type);
|
||||||
Object.keys(node.data.node.template).forEach((key) => {
|
//looking for file fields to prevent saving the content and breaking the flow for exceeding the the data limite for local storage
|
||||||
console.log(node.data.node.template[key].type)
|
Object.keys(node.data.node.template).forEach((key) => {
|
||||||
if(node.data.node.template[key].type==="file"){
|
console.log(node.data.node.template[key].type);
|
||||||
console.log(node.data.node.template[key])
|
if (node.data.node.template[key].type === "file") {
|
||||||
node.data.node.template[key].content = null;
|
console.log(node.data.node.template[key]);
|
||||||
node.data.node.template[key].value = "";
|
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(() => {
|
useEffect(() => {
|
||||||
//get tabs locally saved
|
//get tabs locally saved
|
||||||
let cookie = window.localStorage.getItem("tabsData");
|
let cookie = window.localStorage.getItem("tabsData");
|
||||||
if (cookie && Object.keys(templates).length > 0) {
|
if (cookie && Object.keys(templates).length > 0) {
|
||||||
let cookieObject: LangFlowState = JSON.parse(cookie);
|
let cookieObject: LangFlowState = JSON.parse(cookie);
|
||||||
cookieObject.flows.forEach((flow) => {
|
cookieObject.flows.forEach((flow) => {
|
||||||
flow.data.nodes.forEach((node) => {
|
flow.data.nodes.forEach((node) => {
|
||||||
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
|
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
|
||||||
node.data.node.template = updateTemplate(
|
node.data.node.template = updateTemplate(
|
||||||
templates[node.data.type][
|
templates[node.data.type][
|
||||||
"template"
|
"template"
|
||||||
] as unknown as APITemplateType,
|
] as unknown as APITemplateType,
|
||||||
|
|
||||||
node.data.node.template as APITemplateType
|
node.data.node.template as APITemplateType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
setTabIndex(cookieObject.tabIndex);
|
setTabIndex(cookieObject.tabIndex);
|
||||||
setFlows(cookieObject.flows);
|
setFlows(cookieObject.flows);
|
||||||
setId(cookieObject.id);
|
setId(cookieObject.id);
|
||||||
}
|
}
|
||||||
}, [templates]);
|
}, [templates]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//save tabs locally
|
//save tabs locally
|
||||||
console.log(id)
|
console.log(id);
|
||||||
save();
|
save();
|
||||||
}, [flows, id, tabIndex, newNodeId]);
|
}, [flows, id, tabIndex, newNodeId]);
|
||||||
|
|
||||||
function hardReset() {
|
function hardReset() {
|
||||||
newNodeId.current = uuidv4();
|
newNodeId.current = uuidv4();
|
||||||
setTabIndex(0);
|
setTabIndex(0);
|
||||||
setFlows([]);
|
setFlows([]);
|
||||||
setId(uuidv4());
|
setId(uuidv4());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads the current flow as a JSON file
|
* Downloads the current flow as a JSON file
|
||||||
*/
|
*/
|
||||||
function downloadFlow(flow: FlowType) {
|
function downloadFlow(flow: FlowType) {
|
||||||
// create a data URI with the current flow data
|
// create a data URI with the current flow data
|
||||||
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
||||||
JSON.stringify(flow)
|
JSON.stringify(flow)
|
||||||
)}`;
|
)}`;
|
||||||
|
|
||||||
// create a link element and set its properties
|
// create a link element and set its properties
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = jsonString;
|
link.href = jsonString;
|
||||||
link.download = `${flows[tabIndex].name}.json`;
|
link.download = `${flows[tabIndex].name}.json`;
|
||||||
|
|
||||||
// simulate a click on the link element to trigger the download
|
// simulate a click on the link element to trigger the download
|
||||||
link.click();
|
link.click();
|
||||||
setNoticeData({
|
setNoticeData({
|
||||||
title: "Warning: Critical data,JSON file may including API keys.",
|
title: "Warning: Critical data,JSON file may including API keys.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNodeId() {
|
function getNodeId() {
|
||||||
return `dndnode_` + incrementNodeId();
|
return `dndnode_` + incrementNodeId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a file input and listens to a change event to upload a JSON flow file.
|
* Creates a file input and listens to a change event to upload a JSON flow file.
|
||||||
* If the file type is application/json, the file is read and parsed into a JSON object.
|
* If the file type is application/json, the file is read and parsed into a JSON object.
|
||||||
* The resulting JSON object is passed to the addFlow function.
|
* The resulting JSON object is passed to the addFlow function.
|
||||||
*/
|
*/
|
||||||
function uploadFlow() {
|
function uploadFlow() {
|
||||||
// create a file input
|
// create a file input
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
// add a change event listener to the file input
|
// add a change event listener to the file input
|
||||||
input.onchange = (e: Event) => {
|
input.onchange = (e: Event) => {
|
||||||
// check if the file type is application/json
|
// check if the file type is application/json
|
||||||
if ((e.target as HTMLInputElement).files[0].type === "application/json") {
|
if ((e.target as HTMLInputElement).files[0].type === "application/json") {
|
||||||
// get the file from the file input
|
// get the file from the file input
|
||||||
const file = (e.target as HTMLInputElement).files[0];
|
const file = (e.target as HTMLInputElement).files[0];
|
||||||
// read the file as text
|
// read the file as text
|
||||||
file.text().then((text) => {
|
file.text().then((text) => {
|
||||||
// parse the text into a JSON object
|
// parse the text into a JSON object
|
||||||
let flow: FlowType = JSON.parse(text);
|
let flow: FlowType = JSON.parse(text);
|
||||||
|
|
||||||
addFlow(flow);
|
addFlow(flow);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// trigger the file input click event to open the file dialog
|
// trigger the file input click event to open the file dialog
|
||||||
input.click();
|
input.click();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Removes a flow from an array of flows based on its id.
|
* Removes a flow from an array of flows based on its id.
|
||||||
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
|
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
|
||||||
* @param {string} id - The id of the flow to remove.
|
* @param {string} id - The id of the flow to remove.
|
||||||
*/
|
*/
|
||||||
function removeFlow(id: string) {
|
function removeFlow(id: string) {
|
||||||
setFlows((prevState) => {
|
setFlows((prevState) => {
|
||||||
const newFlows = [...prevState];
|
const newFlows = [...prevState];
|
||||||
const index = newFlows.findIndex((flow) => flow.id === id);
|
const index = newFlows.findIndex((flow) => flow.id === id);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
if (index === tabIndex) {
|
if (index === tabIndex) {
|
||||||
setTabIndex(flows.length - 2);
|
setTabIndex(flows.length - 2);
|
||||||
newFlows.splice(index, 1);
|
newFlows.splice(index, 1);
|
||||||
} else {
|
} else {
|
||||||
let flowId = flows[tabIndex].id;
|
let flowId = flows[tabIndex].id;
|
||||||
newFlows.splice(index, 1);
|
newFlows.splice(index, 1);
|
||||||
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
|
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newFlows;
|
return newFlows;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Add a new flow to the list of flows.
|
* Add a new flow to the list of flows.
|
||||||
* @param flow Optional flow to add.
|
* @param flow Optional flow to add.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function paste(selectionInstance, position){
|
function paste(selectionInstance, position) {
|
||||||
console.log(position);
|
let minimumX = Infinity;
|
||||||
console.log(selectionInstance)
|
let minimumY = Infinity;
|
||||||
let minimumX = Infinity;
|
let idsMap = {};
|
||||||
let minimumY = Infinity;
|
let nodes = reactFlowInstance.getNodes();
|
||||||
let idsMap = {};
|
let edges = reactFlowInstance.getEdges();
|
||||||
let nodes = reactFlowInstance.getNodes();
|
selectionInstance.nodes.forEach((n) => {
|
||||||
let edges = reactFlowInstance.getEdges();
|
if (n.position.y < minimumY) {
|
||||||
selectionInstance.nodes.forEach((n) => {
|
minimumY = n.position.y;
|
||||||
if (n.position.y < minimumY) {
|
}
|
||||||
minimumY = n.position.y;
|
if (n.position.x < minimumX) {
|
||||||
}
|
minimumX = n.position.x;
|
||||||
if (n.position.x < minimumX) {
|
}
|
||||||
minimumX = n.position.x;
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const insidePosition = reactFlowInstance.project(position);
|
const insidePosition = reactFlowInstance.project(position);
|
||||||
|
|
||||||
selectionInstance.nodes.forEach((n) => {
|
|
||||||
// Generate a unique node ID
|
|
||||||
let newId = getNodeId();
|
|
||||||
idsMap[n.id] = newId;
|
|
||||||
|
|
||||||
// Create a new node object
|
|
||||||
const newNode: NodeType = {
|
|
||||||
id: newId,
|
|
||||||
type: "genericNode",
|
|
||||||
position: {
|
|
||||||
x: insidePosition.x + n.position.x - minimumX,
|
|
||||||
y: insidePosition.y + n.position.y - minimumY,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
...n.data,
|
|
||||||
id: newId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the new node to the list of nodes in state
|
|
||||||
nodes = nodes
|
|
||||||
.map((e) => ({ ...e, selected: false }))
|
|
||||||
.concat({ ...newNode, selected: false })
|
|
||||||
console.log(nodes);
|
|
||||||
});
|
|
||||||
reactFlowInstance.setNodes(nodes);
|
|
||||||
|
|
||||||
selectionInstance.edges.forEach((e) => {
|
|
||||||
let source = idsMap[e.source];
|
|
||||||
let target = idsMap[e.target];
|
|
||||||
let sourceHandleSplitted = e.sourceHandle.split("|");
|
|
||||||
let sourceHandle =
|
|
||||||
sourceHandleSplitted[0] +
|
|
||||||
"|" +
|
|
||||||
source +
|
|
||||||
"|" +
|
|
||||||
sourceHandleSplitted.slice(2).join("|");
|
|
||||||
let targetHandleSplitted = e.targetHandle.split("|");
|
|
||||||
let targetHandle =
|
|
||||||
targetHandleSplitted.slice(0, -1).join("|") + "|" + target;
|
|
||||||
let id =
|
|
||||||
"reactflow__edge-" +
|
|
||||||
source +
|
|
||||||
sourceHandle +
|
|
||||||
"-" +
|
|
||||||
target +
|
|
||||||
targetHandle;
|
|
||||||
edges = addEdge(
|
|
||||||
{
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
sourceHandle,
|
|
||||||
targetHandle,
|
|
||||||
id,
|
|
||||||
className: "animate-pulse",
|
|
||||||
selected: false,
|
|
||||||
},
|
|
||||||
edges.map((e) => ({ ...e, selected: false }))
|
|
||||||
);
|
|
||||||
console.log(edges);
|
|
||||||
});
|
|
||||||
reactFlowInstance.setEdges(edges);
|
|
||||||
};
|
|
||||||
|
|
||||||
function addFlow(flow?: FlowType) {
|
|
||||||
// Get data from the flow or set it to null if there's no flow provided.
|
|
||||||
const data = flow?.data ? flow.data : null;
|
|
||||||
const description = flow?.description ? flow.description : "";
|
|
||||||
|
|
||||||
if (data) {
|
selectionInstance.nodes.forEach((n) => {
|
||||||
data.nodes.forEach((node) => {
|
// Generate a unique node ID
|
||||||
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
|
let newId = getNodeId();
|
||||||
node.data.node.template = updateTemplate(
|
idsMap[n.id] = newId;
|
||||||
templates[node.data.type]["template"] as unknown as APITemplateType,
|
|
||||||
node.data.node.template as APITemplateType
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Create a new flow with a default name if no flow is provided.
|
|
||||||
let newFlow: FlowType = {
|
|
||||||
description,
|
|
||||||
name: flow?.name ?? "New Flow",
|
|
||||||
id: uuidv4(),
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Increment the ID counter.
|
// Create a new node object
|
||||||
setId(uuidv4());
|
const newNode: NodeType = {
|
||||||
|
id: newId,
|
||||||
|
type: "genericNode",
|
||||||
|
position: {
|
||||||
|
x: insidePosition.x + n.position.x - minimumX,
|
||||||
|
y: insidePosition.y + n.position.y - minimumY,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...n.data,
|
||||||
|
id: newId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Add the new flow to the list of flows.
|
// Add the new node to the list of nodes in state
|
||||||
|
nodes = nodes
|
||||||
setFlows((prevState) => {
|
.map((e) => ({ ...e, selected: false }))
|
||||||
const newFlows = [...prevState, newFlow];
|
.concat({ ...newNode, selected: false });
|
||||||
return newFlows;
|
});
|
||||||
});
|
reactFlowInstance.setNodes(nodes);
|
||||||
|
|
||||||
// Set the tab index to the new flow.
|
selectionInstance.edges.forEach((e) => {
|
||||||
setTabIndex(flows.length);
|
let source = idsMap[e.source];
|
||||||
}
|
let target = idsMap[e.target];
|
||||||
/**
|
let sourceHandleSplitted = e.sourceHandle.split("|");
|
||||||
* Updates an existing flow with new data
|
let sourceHandle =
|
||||||
* @param newFlow - The new flow object containing the updated data
|
sourceHandleSplitted[0] +
|
||||||
*/
|
"|" +
|
||||||
function updateFlow(newFlow: FlowType) {
|
source +
|
||||||
setFlows((prevState) => {
|
"|" +
|
||||||
const newFlows = [...prevState];
|
sourceHandleSplitted.slice(2).join("|");
|
||||||
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
|
let targetHandleSplitted = e.targetHandle.split("|");
|
||||||
if (index !== -1) {
|
let targetHandle =
|
||||||
newFlows[index].description = newFlow.description ?? "";
|
targetHandleSplitted.slice(0, -1).join("|") + "|" + target;
|
||||||
newFlows[index].data = newFlow.data;
|
let id =
|
||||||
newFlows[index].name = newFlow.name;
|
"reactflow__edge-" +
|
||||||
}
|
source +
|
||||||
return newFlows;
|
sourceHandle +
|
||||||
});
|
"-" +
|
||||||
}
|
target +
|
||||||
const [disableCopyPaste, setDisableCopyPaste] = useState(false);
|
targetHandle;
|
||||||
|
edges = addEdge(
|
||||||
|
{
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
sourceHandle,
|
||||||
|
targetHandle,
|
||||||
|
id,
|
||||||
|
className: "animate-pulse",
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
edges.map((e) => ({ ...e, selected: false }))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
reactFlowInstance.setEdges(edges);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
function addFlow(flow?: FlowType) {
|
||||||
<TabsContext.Provider
|
// Get data from the flow or set it to null if there's no flow provided.
|
||||||
value={{
|
const data = flow?.data ? flow.data : null;
|
||||||
disableCopyPaste,
|
const description = flow?.description ? flow.description : "";
|
||||||
setDisableCopyPaste,
|
|
||||||
save,
|
if (data) {
|
||||||
hardReset,
|
data.nodes.forEach((node) => {
|
||||||
tabIndex,
|
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
|
||||||
setTabIndex,
|
node.data.node.template = updateTemplate(
|
||||||
flows,
|
templates[node.data.type]["template"] as unknown as APITemplateType,
|
||||||
incrementNodeId,
|
node.data.node.template as APITemplateType
|
||||||
removeFlow,
|
);
|
||||||
addFlow,
|
}
|
||||||
updateFlow,
|
});
|
||||||
downloadFlow,
|
}
|
||||||
uploadFlow,
|
// Create a new flow with a default name if no flow is provided.
|
||||||
getNodeId,
|
let newFlow: FlowType = {
|
||||||
paste,
|
description,
|
||||||
}}
|
name: flow?.name ?? "New Flow",
|
||||||
>
|
id: uuidv4(),
|
||||||
{children}
|
data,
|
||||||
</TabsContext.Provider>
|
};
|
||||||
);
|
|
||||||
}
|
// Increment the ID counter.
|
||||||
|
setId(uuidv4());
|
||||||
|
|
||||||
|
// Add the new flow to the list of flows.
|
||||||
|
|
||||||
|
setFlows((prevState) => {
|
||||||
|
const newFlows = [...prevState, newFlow];
|
||||||
|
return newFlows;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the tab index to the new flow.
|
||||||
|
setTabIndex(flows.length);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Updates an existing flow with new data
|
||||||
|
* @param newFlow - The new flow object containing the updated data
|
||||||
|
*/
|
||||||
|
function updateFlow(newFlow: FlowType) {
|
||||||
|
setFlows((prevState) => {
|
||||||
|
const newFlows = [...prevState];
|
||||||
|
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
newFlows[index].description = newFlow.description ?? "";
|
||||||
|
newFlows[index].data = newFlow.data;
|
||||||
|
newFlows[index].name = newFlow.name;
|
||||||
|
}
|
||||||
|
return newFlows;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const [disableCopyPaste, setDisableCopyPaste] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TabsContext.Provider
|
||||||
|
value={{
|
||||||
|
disableCopyPaste,
|
||||||
|
setDisableCopyPaste,
|
||||||
|
save,
|
||||||
|
hardReset,
|
||||||
|
tabIndex,
|
||||||
|
setTabIndex,
|
||||||
|
flows,
|
||||||
|
incrementNodeId,
|
||||||
|
removeFlow,
|
||||||
|
addFlow,
|
||||||
|
updateFlow,
|
||||||
|
downloadFlow,
|
||||||
|
uploadFlow,
|
||||||
|
getNodeId,
|
||||||
|
paste,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TabsContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,40 +4,40 @@ import axios, { AxiosResponse } from "axios";
|
||||||
import { FlowType } from "../../types/flow";
|
import { FlowType } from "../../types/flow";
|
||||||
|
|
||||||
export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
|
export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
|
||||||
return await axios.get(`/all`);
|
return await axios.get(`/all`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendAll(data: sendAllProps) {
|
export async function sendAll(data: sendAllProps) {
|
||||||
return await axios.post(`/predict`, data);
|
return await axios.post(`/predict`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkCode(
|
export async function checkCode(
|
||||||
code: string
|
code: string
|
||||||
): Promise<AxiosResponse<errorsTypeAPI>> {
|
): Promise<AxiosResponse<errorsTypeAPI>> {
|
||||||
return await axios.post("/validate/code", { code });
|
return await axios.post("/validate/code", { code });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkPrompt(
|
export async function checkPrompt(
|
||||||
template: string
|
template: string
|
||||||
): Promise<AxiosResponse<PromptTypeAPI>> {
|
): Promise<AxiosResponse<PromptTypeAPI>> {
|
||||||
return await axios.post("/validate/prompt", { template });
|
return await axios.post("/validate/prompt", { template });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getExamples(): Promise<FlowType[]> {
|
export async function getExamples(): Promise<FlowType[]> {
|
||||||
const url =
|
const url =
|
||||||
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples";
|
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples";
|
||||||
const response = await axios.get(url);
|
const response = await axios.get(url);
|
||||||
|
|
||||||
const jsonFiles = response.data.filter((file: any) => {
|
const jsonFiles = response.data.filter((file: any) => {
|
||||||
return file.name.endsWith(".json");
|
return file.name.endsWith(".json");
|
||||||
});
|
});
|
||||||
|
|
||||||
const contentsPromises = jsonFiles.map(async (file: any) => {
|
const contentsPromises = jsonFiles.map(async (file: any) => {
|
||||||
const contentResponse = await axios.get(file.download_url);
|
const contentResponse = await axios.get(file.download_url);
|
||||||
return contentResponse.data;
|
return contentResponse.data;
|
||||||
});
|
});
|
||||||
|
|
||||||
const contents = await Promise.all(contentsPromises);
|
const contents = await Promise.all(contentsPromises);
|
||||||
|
|
||||||
return contents;
|
return contents;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,16 @@ 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
|
||||||
);
|
);
|
||||||
root.render(
|
root.render(
|
||||||
<ContextWrapper>
|
<ContextWrapper>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</ContextWrapper>
|
</ContextWrapper>
|
||||||
);
|
);
|
||||||
reportWebVitals();
|
reportWebVitals();
|
||||||
|
|
|
||||||
|
|
@ -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,42 +13,40 @@ 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);
|
||||||
const { dark } = useContext(darkContext);
|
const { dark } = useContext(darkContext);
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
||||||
|
|
||||||
const copyToClipboard = () => {
|
const copyToClipboard = () => {
|
||||||
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
navigator.clipboard.writeText(tabs[activeTab].code).then(() => {
|
navigator.clipboard.writeText(tabs[activeTab].code).then(() => {
|
||||||
setIsCopied(true);
|
setIsCopied(true);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsCopied(false);
|
setIsCopied(false);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
setOpen(x);
|
setOpen(x);
|
||||||
if (x === false) {
|
if (x === false) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
closePopUp();
|
closePopUp();
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pythonApiCode = `import requests
|
const pythonApiCode = `import requests
|
||||||
import json
|
import json
|
||||||
|
|
||||||
API_URL = "${window.location.protocol}//${window.location.host}/predict"
|
API_URL = "${window.location.protocol}//${window.location.host}/predict"
|
||||||
|
|
@ -58,130 +60,142 @@ def predict(message):
|
||||||
|
|
||||||
print(predict("Your message"))`;
|
print(predict("Your message"))`;
|
||||||
|
|
||||||
const pythonCode = `from langflow import load_flow_from_json
|
const pythonCode = `from langflow import load_flow_from_json
|
||||||
|
|
||||||
flow = load_flow_from_json("${flowName}.json")
|
flow = load_flow_from_json("${flowName}.json")
|
||||||
# Now you can use it like any chain
|
# Now you can use it like any chain
|
||||||
flow("Hey, have you heard of LangFlow?")`;
|
flow("Hey, have you heard of LangFlow?")`;
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
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:
|
||||||
code: pythonApiCode,
|
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
|
||||||
},
|
code: pythonApiCode,
|
||||||
{
|
},
|
||||||
name: "Python Code",
|
{
|
||||||
mode: "python",
|
name: "Python Code",
|
||||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
mode: "python",
|
||||||
code: pythonCode,
|
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||||
},
|
code: pythonCode,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
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">
|
||||||
return (
|
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
<Transition.Child
|
||||||
<Dialog
|
as={Fragment}
|
||||||
as="div"
|
enter="ease-out duration-300"
|
||||||
className="relative z-10"
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
onClose={setModalOpen}
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
initialFocus={ref}
|
leave="ease-in duration-200"
|
||||||
>
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
<Transition.Child
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
as={Fragment}
|
>
|
||||||
enter="ease-out duration-300"
|
<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]">
|
||||||
enterFrom="opacity-0"
|
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||||
enterTo="opacity-100"
|
<button
|
||||||
leave="ease-in duration-200"
|
type="button"
|
||||||
leaveFrom="opacity-100"
|
className="rounded-md text-gray-400 hover:text-gray-500"
|
||||||
leaveTo="opacity-0"
|
onClick={() => {
|
||||||
>
|
setModalOpen(false);
|
||||||
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
}}
|
||||||
</Transition.Child>
|
>
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||||
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
</button>
|
||||||
<Transition.Child
|
</div>
|
||||||
as={Fragment}
|
<div className="h-full w-full flex flex-col justify-center items-center">
|
||||||
enter="ease-out duration-300"
|
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
|
||||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
<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">
|
||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
<CodeBracketSquareIcon
|
||||||
leave="ease-in duration-200"
|
className="h-6 w-6 text-blue-600"
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
aria-hidden="true"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
/>
|
||||||
>
|
</div>
|
||||||
<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="mt-4 text-center sm:ml-4 sm:text-left">
|
||||||
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
<Dialog.Title
|
||||||
<button
|
as="h3"
|
||||||
type="button"
|
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
||||||
className="rounded-md text-gray-400 hover:text-gray-500"
|
>
|
||||||
onClick={() => {
|
Code
|
||||||
setModalOpen(false);
|
</Dialog.Title>
|
||||||
}}
|
</div>
|
||||||
>
|
</div>
|
||||||
<span className="sr-only">Close</span>
|
<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">
|
||||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
<div className="flex flex-col h-full w-full ">
|
||||||
</button>
|
<div className="flex px-5 z-10">
|
||||||
</div>
|
{tabs.map((tab, index) => (
|
||||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
<button
|
||||||
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
|
onClick={() => {
|
||||||
<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">
|
setActiveTab(index);
|
||||||
<CodeBracketSquareIcon
|
}}
|
||||||
className="h-6 w-6 text-blue-600"
|
className={
|
||||||
aria-hidden="true"
|
"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
|
||||||
</div>
|
? " bg-white dark:bg-gray-800"
|
||||||
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
: "bg-gray-100 dark:bg-gray-900")
|
||||||
<Dialog.Title
|
}
|
||||||
as="h3"
|
>
|
||||||
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
{tab.name}
|
||||||
>
|
<img src={tab.image} className="w-6" />
|
||||||
Code
|
</button>
|
||||||
</Dialog.Title>
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full h-full rounded-lg shadow bg-white dark:bg-gray-800">
|
||||||
<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="w-full flex items-center justify-between mb-2">
|
||||||
<div className="flex flex-col h-full w-full ">
|
<span className="text-sm text-gray-500 dark:text-gray-300">
|
||||||
<div className="flex px-5 z-10">
|
Export your flow to use it with this code.
|
||||||
{tabs.map((tab, index) => (
|
</span>
|
||||||
<button onClick={() => {
|
<button
|
||||||
setActiveTab(index);
|
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
|
||||||
}} 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")}>
|
onClick={copyToClipboard}
|
||||||
{tab.name}
|
>
|
||||||
<img src={tab.image} className="w-6" />
|
{isCopied ? (
|
||||||
</button>
|
<IconCheck size={18} />
|
||||||
))}
|
) : (
|
||||||
</div>
|
<IconClipboard size={18} />
|
||||||
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full h-full rounded-lg shadow bg-white dark:bg-gray-800">
|
)}
|
||||||
<div className="w-full flex items-center justify-between mb-2">
|
{isCopied ? "Copied!" : "Copy code"}
|
||||||
<span className="text-sm text-gray-500 dark:text-gray-300">
|
</button>
|
||||||
Export your flow to use it with this code.
|
</div>
|
||||||
</span>
|
<SyntaxHighlighter
|
||||||
<button
|
className="h-[370px]"
|
||||||
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
|
language={tabs[activeTab].mode}
|
||||||
onClick={copyToClipboard}
|
style={oneDark}
|
||||||
>
|
customStyle={{ margin: 0 }}
|
||||||
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}
|
>
|
||||||
{isCopied ? 'Copied!' : 'Copy code'}
|
{tabs[activeTab].code}
|
||||||
</button>
|
</SyntaxHighlighter>
|
||||||
</div>
|
</div>
|
||||||
<SyntaxHighlighter
|
</div>
|
||||||
className="h-[370px]"
|
</div>
|
||||||
language={tabs[activeTab].mode}
|
</div>
|
||||||
style={oneDark}
|
</Dialog.Panel>
|
||||||
customStyle={{ margin: 0 }}
|
</Transition.Child>
|
||||||
>
|
</div>
|
||||||
{tabs[activeTab].code}
|
</div>
|
||||||
</SyntaxHighlighter>
|
</Dialog>
|
||||||
</div>
|
</Transition.Root>
|
||||||
</div>
|
);
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition.Root>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,154 +13,154 @@ import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
||||||
import { classNames } from "../../../../utils";
|
import { classNames } from "../../../../utils";
|
||||||
|
|
||||||
export default function ModalField({ data, title, required, id, name, type }) {
|
export default function ModalField({ data, title, required, id, name, type }) {
|
||||||
const { save } = useContext(TabsContext);
|
const { save } = useContext(TabsContext);
|
||||||
const [enabled, setEnabled] = useState(
|
const [enabled, setEnabled] = useState(
|
||||||
data.node.template[name]?.value ?? false
|
data.node.template[name]?.value ?? false
|
||||||
);
|
);
|
||||||
const display =
|
const display =
|
||||||
type === "str" ||
|
type === "str" ||
|
||||||
type === "int" ||
|
type === "int" ||
|
||||||
type === "prompt" ||
|
type === "prompt" ||
|
||||||
type === "bool" ||
|
type === "bool" ||
|
||||||
type === "float" ||
|
type === "float" ||
|
||||||
type === "file" ||
|
type === "file" ||
|
||||||
type === "code";
|
type === "code";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"flex flex-row w-full items-center justify-between",
|
"flex flex-row w-full items-center justify-between",
|
||||||
display ? "" : "hidden"
|
display ? "" : "hidden"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{display && (
|
{display && (
|
||||||
<div>
|
<div>
|
||||||
<span className="mx-2 dark:text-gray-300">{title}</span>
|
<span className="mx-2 dark:text-gray-300">{title}</span>
|
||||||
<span className="text-red-600">{required ? " *" : ""}</span>
|
<span className="text-red-600">{required ? " *" : ""}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{type === "str" && !data.node.template[name].options ? (
|
{type === "str" && !data.node.template[name].options ? (
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
{data.node.template[name].list ? (
|
{data.node.template[name].list ? (
|
||||||
<InputListComponent
|
<InputListComponent
|
||||||
disabled={false}
|
disabled={false}
|
||||||
value={
|
value={
|
||||||
!data.node.template[name].value ||
|
!data.node.template[name].value ||
|
||||||
data.node.template[name].value === ""
|
data.node.template[name].value === ""
|
||||||
? [""]
|
? [""]
|
||||||
: data.node.template[name].value
|
: data.node.template[name].value
|
||||||
}
|
}
|
||||||
onChange={(t: string[]) => {
|
onChange={(t: string[]) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : data.node.template[name].multiline ? (
|
) : data.node.template[name].multiline ? (
|
||||||
<TextAreaComponent
|
<TextAreaComponent
|
||||||
disabled={false}
|
disabled={false}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t: string) => {
|
onChange={(t: string) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<InputComponent
|
<InputComponent
|
||||||
disabled={false}
|
disabled={false}
|
||||||
password={data.node.template[name].password ?? false}
|
password={data.node.template[name].password ?? false}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t) => {
|
onChange={(t) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : type === "bool" ? (
|
) : type === "bool" ? (
|
||||||
<div className="ml-auto">
|
<div className="ml-auto">
|
||||||
{" "}
|
{" "}
|
||||||
<ToggleComponent
|
<ToggleComponent
|
||||||
disabled={false}
|
disabled={false}
|
||||||
enabled={enabled}
|
enabled={enabled}
|
||||||
setEnabled={(t) => {
|
setEnabled={(t) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
setEnabled(t);
|
setEnabled(t);
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : type === "float" ? (
|
) : type === "float" ? (
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
<FloatComponent
|
<FloatComponent
|
||||||
disabled={false}
|
disabled={false}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t) => {
|
onChange={(t) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : type === "str" && data.node.template[name].options ? (
|
) : type === "str" && data.node.template[name].options ? (
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
options={data.node.template[name].options}
|
options={data.node.template[name].options}
|
||||||
onSelect={(newValue) => (data.node.template[name].value = newValue)}
|
onSelect={(newValue) => (data.node.template[name].value = newValue)}
|
||||||
value={data.node.template[name].value ?? "Choose an option"}
|
value={data.node.template[name].value ?? "Choose an option"}
|
||||||
></Dropdown>
|
></Dropdown>
|
||||||
</div>
|
</div>
|
||||||
) : type === "int" ? (
|
) : type === "int" ? (
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
<IntComponent
|
<IntComponent
|
||||||
disabled={false}
|
disabled={false}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t) => {
|
onChange={(t) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : type === "file" ? (
|
) : type === "file" ? (
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
<InputFileComponent
|
<InputFileComponent
|
||||||
disabled={false}
|
disabled={false}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t: string) => {
|
onChange={(t: string) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
}}
|
}}
|
||||||
fileTypes={data.node.template[name].fileTypes}
|
fileTypes={data.node.template[name].fileTypes}
|
||||||
suffixes={data.node.template[name].suffixes}
|
suffixes={data.node.template[name].suffixes}
|
||||||
onFileChange={(t: string) => {
|
onFileChange={(t: string) => {
|
||||||
data.node.template[name].content = t;
|
data.node.template[name].content = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
></InputFileComponent>
|
></InputFileComponent>
|
||||||
</div>
|
</div>
|
||||||
) : type === "prompt" ? (
|
) : type === "prompt" ? (
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
<PromptAreaComponent
|
<PromptAreaComponent
|
||||||
disabled={false}
|
disabled={false}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t: string) => {
|
onChange={(t: string) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : type === "code" ? (
|
) : type === "code" ? (
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
<CodeAreaComponent
|
<CodeAreaComponent
|
||||||
disabled={false}
|
disabled={false}
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t: string) => {
|
onChange={(t: string) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="hidden"></div>
|
<div className="hidden"></div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,138 +8,138 @@ import { typesContext } from "../../contexts/typesContext";
|
||||||
import ModalField from "./components/ModalField";
|
import ModalField from "./components/ModalField";
|
||||||
|
|
||||||
export default function NodeModal({ data }: { data: NodeDataType }) {
|
export default function NodeModal({ data }: { data: NodeDataType }) {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const { types } = useContext(typesContext);
|
const { types } = useContext(typesContext);
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
setOpen(x);
|
setOpen(x);
|
||||||
if (x === false) {
|
if (x === false) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
closePopUp();
|
closePopUp();
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const Icon = nodeIcons[types[data.type]];
|
const Icon = nodeIcons[types[data.type]];
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||||
<Dialog
|
<Dialog
|
||||||
as="div"
|
as="div"
|
||||||
className="relative z-10"
|
className="relative z-10"
|
||||||
onClose={setModalOpen}
|
onClose={setModalOpen}
|
||||||
initialFocus={ref}
|
initialFocus={ref}
|
||||||
>
|
>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
<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">
|
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
<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">
|
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-md text-gray-400 hover:text-gray-500"
|
className="rounded-md text-gray-400 hover:text-gray-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
<div className="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="flex w-full pb-4 z-10 justify-center shadow-sm">
|
||||||
<Icon
|
<Icon
|
||||||
className="w-10 mt-4 h-10 p-1 rounded"
|
className="w-10 mt-4 h-10 p-1 rounded"
|
||||||
style={{
|
style={{
|
||||||
color:
|
color:
|
||||||
nodeColors[types[data.type]] ?? nodeColors.unknown,
|
nodeColors[types[data.type]] ?? nodeColors.unknown,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
||||||
>
|
>
|
||||||
{data.type}
|
{data.type}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
|
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
|
||||||
<div className="flex h-full w-full">
|
<div className="flex h-full w-full">
|
||||||
<div className="overflow-hidden px-4 sm:p-4 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
<div className="overflow-hidden px-4 sm:p-4 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
||||||
<div className="flex flex-col h-full gap-5">
|
<div className="flex flex-col h-full gap-5">
|
||||||
{Object.keys(data.node.template)
|
{Object.keys(data.node.template)
|
||||||
.filter(
|
.filter(
|
||||||
(t) =>
|
(t) =>
|
||||||
t.charAt(0) !== "_" &&
|
t.charAt(0) !== "_" &&
|
||||||
data.node.template[t].advanced &&
|
data.node.template[t].advanced &&
|
||||||
data.node.template[t].show
|
data.node.template[t].show
|
||||||
)
|
)
|
||||||
.map((t: string, idx) => {
|
.map((t: string, idx) => {
|
||||||
return (
|
return (
|
||||||
<ModalField
|
<ModalField
|
||||||
key={idx}
|
key={idx}
|
||||||
data={data}
|
data={data}
|
||||||
title={
|
title={
|
||||||
data.node.template[t].display_name
|
data.node.template[t].display_name
|
||||||
? data.node.template[t].display_name
|
? data.node.template[t].display_name
|
||||||
: data.node.template[t].name
|
: data.node.template[t].name
|
||||||
? toNormalCase(data.node.template[t].name)
|
? toNormalCase(data.node.template[t].name)
|
||||||
: toNormalCase(t)
|
: toNormalCase(t)
|
||||||
}
|
}
|
||||||
required={data.node.template[t].required}
|
required={data.node.template[t].required}
|
||||||
id={
|
id={
|
||||||
data.node.template[t].type +
|
data.node.template[t].type +
|
||||||
"|" +
|
"|" +
|
||||||
t +
|
t +
|
||||||
"|" +
|
"|" +
|
||||||
data.id
|
data.id
|
||||||
}
|
}
|
||||||
name={t}
|
name={t}
|
||||||
type={data.node.template[t].type}
|
type={data.node.template[t].type}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
|
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
|
||||||
<button
|
<button
|
||||||
type="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"
|
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={() => {
|
onClick={() => {
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Done
|
Done
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -5,78 +5,78 @@ import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||||
import { programmingLanguages } from "../../../../utils";
|
import { programmingLanguages } from "../../../../utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
language: string;
|
language: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
export const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
||||||
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
||||||
|
|
||||||
const copyToClipboard = () => {
|
const copyToClipboard = () => {
|
||||||
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
navigator.clipboard.writeText(value).then(() => {
|
navigator.clipboard.writeText(value).then(() => {
|
||||||
setIsCopied(true);
|
setIsCopied(true);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsCopied(false);
|
setIsCopied(false);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const downloadAsFile = () => {
|
const downloadAsFile = () => {
|
||||||
const fileExtension = programmingLanguages[language] || ".file";
|
const fileExtension = programmingLanguages[language] || ".file";
|
||||||
const suggestedFileName = `${"generated-code"}${fileExtension}`;
|
const suggestedFileName = `${"generated-code"}${fileExtension}`;
|
||||||
const fileName = window.prompt("enter file name", suggestedFileName);
|
const fileName = window.prompt("enter file name", suggestedFileName);
|
||||||
|
|
||||||
if (!fileName) {
|
if (!fileName) {
|
||||||
// user pressed cancel on prompt
|
// user pressed cancel on prompt
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = new Blob([value], { type: "text/plain" });
|
const blob = new Blob([value], { type: "text/plain" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.download = fileName;
|
link.download = fileName;
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.style.display = "none";
|
link.style.display = "none";
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="codeblock font-sans text-[16px]">
|
<div className="codeblock font-sans text-[16px]">
|
||||||
<div className="flex items-center justify-between py-1.5 px-4">
|
<div className="flex items-center justify-between py-1.5 px-4">
|
||||||
<span className="text-xs lowercase text-white">{language}</span>
|
<span className="text-xs lowercase text-white">{language}</span>
|
||||||
|
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<button
|
<button
|
||||||
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-white"
|
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-white"
|
||||||
onClick={copyToClipboard}
|
onClick={copyToClipboard}
|
||||||
>
|
>
|
||||||
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}
|
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}
|
||||||
{isCopied ? "Copied!" : "Copy code"}
|
{isCopied ? "Copied!" : "Copy code"}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="flex items-center rounded bg-none p-1 text-xs text-white"
|
className="flex items-center rounded bg-none p-1 text-xs text-white"
|
||||||
onClick={downloadAsFile}
|
onClick={downloadAsFile}
|
||||||
>
|
>
|
||||||
<IconDownload size={18} />
|
<IconDownload size={18} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
className=" w-[570px]"
|
className=" w-[570px]"
|
||||||
language={language}
|
language={language}
|
||||||
style={oneDark}
|
style={oneDark}
|
||||||
customStyle={{ margin: 0 }}
|
customStyle={{ margin: 0 }}
|
||||||
>
|
>
|
||||||
{value}
|
{value}
|
||||||
</SyntaxHighlighter>
|
</SyntaxHighlighter>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
CodeBlock.displayName = "CodeBlock";
|
CodeBlock.displayName = "CodeBlock";
|
||||||
|
|
|
||||||
|
|
@ -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,132 +13,154 @@ 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({
|
||||||
const convert = new Convert({ newline: true });
|
chat,
|
||||||
const [message, setMessage] = useState("");
|
lockChat,
|
||||||
const imgRef = useRef(null);
|
}: {
|
||||||
useEffect(() => {
|
chat: ChatMessageType;
|
||||||
setMessage(chat.message);
|
lockChat: boolean;
|
||||||
}, [chat.message]);
|
}) {
|
||||||
const [hidden, setHidden] = useState(true);
|
const convert = new Convert({ newline: true });
|
||||||
return (
|
const [message, setMessage] = useState("");
|
||||||
<div
|
const imgRef = useRef(null);
|
||||||
className={classNames(
|
useEffect(() => {
|
||||||
"w-full py-2 pl-2 flex",
|
setMessage(chat.message);
|
||||||
chat.isSend
|
}, [chat.message]);
|
||||||
? "bg-white dark:bg-gray-900 "
|
const [hidden, setHidden] = useState(true);
|
||||||
: "bg-gray-200 dark:bg-gray-800"
|
return (
|
||||||
)}
|
<div
|
||||||
>
|
className={classNames(
|
||||||
<div
|
"w-full py-2 pl-2 flex",
|
||||||
className={classNames(
|
chat.isSend
|
||||||
"rounded-full overflow-hidden w-8 h-8 flex items-center my-3 justify-center"
|
? "bg-white dark:bg-gray-900 "
|
||||||
)}
|
: "bg-gray-200 dark:bg-gray-800"
|
||||||
>
|
)}
|
||||||
{!chat.isSend && <div className="relative w-8 h-8">
|
>
|
||||||
<img className={"absolute transition-opacity duration-500 scale-150 " + (lockChat ? "opacity-100" : "opacity-0")} src={AiIcon} />
|
<div
|
||||||
<img className={"absolute transition-opacity duration-500 scale-150 " + (lockChat ? "opacity-0" : "opacity-100")} src={AiIconStill} />
|
className={classNames(
|
||||||
</div>}
|
"rounded-full overflow-hidden w-8 h-8 flex items-center my-3 justify-center"
|
||||||
{chat.isSend && <UserIcon className="w-6 h-6 -mb-1 text-gray-800 dark:text-gray-200" />}
|
)}
|
||||||
</div>
|
>
|
||||||
{!chat.isSend ? (
|
{!chat.isSend && (
|
||||||
<div className="w-full text-start flex items-center">
|
<div className="relative w-8 h-8">
|
||||||
<div className="w-full relative text-start inline-block text-gray-600 dark:text-gray-300 text-sm font-normal">
|
<img
|
||||||
{hidden && chat.thought && chat.thought !== "" && (
|
className={
|
||||||
<div
|
"absolute transition-opacity duration-500 scale-150 " +
|
||||||
onClick={() => setHidden((prev) => !prev)}
|
(lockChat ? "opacity-100" : "opacity-0")
|
||||||
className="absolute -top-1 -left-2 cursor-pointer"
|
}
|
||||||
>
|
src={AiIcon}
|
||||||
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce dark:text-white" />
|
/>
|
||||||
</div>
|
<img
|
||||||
)}
|
className={
|
||||||
{chat.thought && chat.thought !== "" && !hidden && (
|
"absolute transition-opacity duration-500 scale-150 " +
|
||||||
<div
|
(lockChat ? "opacity-0" : "opacity-100")
|
||||||
onClick={() => setHidden((prev) => !prev)}
|
}
|
||||||
className=" text-start inline-block rounded-md text-gray-600 dark:text-gray-200 h-full border border-gray-300 dark:border-gray-500
|
src={AiIconStill}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{chat.isSend && (
|
||||||
|
<UserIcon className="w-6 h-6 -mb-1 text-gray-800 dark:text-gray-200" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!chat.isSend ? (
|
||||||
|
<div className="w-full text-start flex items-center">
|
||||||
|
<div className="w-full relative text-start inline-block text-gray-600 dark:text-gray-300 text-sm font-normal">
|
||||||
|
{hidden && chat.thought && chat.thought !== "" && (
|
||||||
|
<div
|
||||||
|
onClick={() => setHidden((prev) => !prev)}
|
||||||
|
className="absolute -top-1 -left-2 cursor-pointer"
|
||||||
|
>
|
||||||
|
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce dark:text-white" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{chat.thought && chat.thought !== "" && !hidden && (
|
||||||
|
<div
|
||||||
|
onClick={() => setHidden((prev) => !prev)}
|
||||||
|
className=" text-start inline-block rounded-md text-gray-600 dark:text-gray-200 h-full border border-gray-300 dark:border-gray-500
|
||||||
bg-gray-100 dark:bg-gray-800 w-[95%] pb-3 pt-3 px-2 ml-3 cursor-pointer scrollbar-hide overflow-scroll"
|
bg-gray-100 dark:bg-gray-800 w-[95%] pb-3 pt-3 px-2 ml-3 cursor-pointer scrollbar-hide overflow-scroll"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: convert.toHtml(chat.thought),
|
__html: convert.toHtml(chat.thought),
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
)}
|
)}
|
||||||
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
||||||
<div className="w-full px-4 pb-3 pt-3 pr-8">
|
<div className="w-full px-4 pb-3 pt-3 pr-8">
|
||||||
<div className="dark:text-white w-full">
|
<div className="dark:text-white w-full">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm, remarkMath]}
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
rehypePlugins={[rehypeMathjax]}
|
rehypePlugins={[rehypeMathjax]}
|
||||||
className="markdown prose dark:prose-invert text-gray-600 dark:text-gray-200"
|
className="markdown prose dark:prose-invert text-gray-600 dark:text-gray-200"
|
||||||
components={{
|
components={{
|
||||||
code({ node, inline, className, children, ...props }) {
|
code({ node, inline, className, children, ...props }) {
|
||||||
if (children.length) {
|
if (children.length) {
|
||||||
if (children[0] == "▍") {
|
if (children[0] == "▍") {
|
||||||
return (
|
return (
|
||||||
<span className="animate-pulse cursor-default mt-1">
|
<span className="animate-pulse cursor-default mt-1">
|
||||||
▍
|
▍
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
children[0] = (children[0] as string).replace(
|
children[0] = (children[0] as string).replace(
|
||||||
"`▍`",
|
"`▍`",
|
||||||
"▍"
|
"▍"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const match = /language-(\w+)/.exec(className || "");
|
const match = /language-(\w+)/.exec(className || "");
|
||||||
|
|
||||||
return !inline ? (
|
return !inline ? (
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
key={Math.random()}
|
key={Math.random()}
|
||||||
language={(match && match[1]) || ""}
|
language={(match && match[1]) || ""}
|
||||||
value={String(children).replace(/\n$/, "")}
|
value={String(children).replace(/\n$/, "")}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<code className={className} {...props}>
|
<code className={className} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</code>
|
</code>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{message}
|
{message}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
{chat.files && (
|
{chat.files && (
|
||||||
<div className="my-2 w-full">
|
<div className="my-2 w-full">
|
||||||
{chat.files.map((file, index) => {
|
{chat.files.map((file, index) => {
|
||||||
return (
|
return (
|
||||||
<div key={index} className="my-2 w-full">
|
<div key={index} className="my-2 w-full">
|
||||||
<FileCard
|
<FileCard
|
||||||
fileName={"Generated File"}
|
fileName={"Generated File"}
|
||||||
fileType={file.data_type}
|
fileType={file.data_type}
|
||||||
content={file.data}
|
content={file.data}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full flex items-center">
|
<div className="w-full flex items-center">
|
||||||
<div className="text-start inline-block px-3 text-sm text-gray-600 dark:text-white">
|
<div className="text-start inline-block px-3 text-sm text-gray-600 dark:text-white">
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm, remarkMath]}
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
rehypePlugins={[rehypeMathjax]}
|
rehypePlugins={[rehypeMathjax]}
|
||||||
className="markdown prose dark:prose-invert text-gray-600 dark:text-gray-200"
|
className="markdown prose dark:prose-invert text-gray-600 dark:text-gray-200"
|
||||||
>
|
>
|
||||||
{message}
|
{message}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,77 +3,77 @@ import * as base64js from "base64-js";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export default function FileCard({ fileName, content, fileType }) {
|
export default function FileCard({ fileName, content, fileType }) {
|
||||||
const handleDownload = () => {
|
const handleDownload = () => {
|
||||||
const byteArray = new Uint8Array(base64js.toByteArray(content));
|
const byteArray = new Uint8Array(base64js.toByteArray(content));
|
||||||
const blob = new Blob([byteArray], { type: "application/octet-stream" });
|
const blob = new Blob([byteArray], { type: "application/octet-stream" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = fileName + ".png";
|
link.download = fileName + ".png";
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
function handleMouseEnter() {
|
function handleMouseEnter() {
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
}
|
}
|
||||||
function handleMouseLeave() {
|
function handleMouseLeave() {
|
||||||
setIsHovered(false);
|
setIsHovered(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileType === "image") {
|
if (fileType === "image") {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative w-1/4 h-1/4"
|
className="relative w-1/4 h-1/4"
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`data:image/png;base64,${content}`}
|
src={`data:image/png;base64,${content}`}
|
||||||
alt="generated image"
|
alt="generated image"
|
||||||
className="rounded-lg w-full h-full"
|
className="rounded-lg w-full h-full"
|
||||||
/>
|
/>
|
||||||
{isHovered && (
|
{isHovered && (
|
||||||
<div
|
<div
|
||||||
className={`absolute top-0 right-0 bg-gray-100 text-gray-700 rounded-bl-lg px-1 text-sm font-bold dark:bg-gray-700 dark:text-gray-300`}
|
className={`absolute top-0 right-0 bg-gray-100 text-gray-700 rounded-bl-lg px-1 text-sm font-bold dark:bg-gray-700 dark:text-gray-300`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="text-gray-500 py-1 px-2 dark:bg-gray-700 dark:text-gray-300"
|
className="text-gray-500 py-1 px-2 dark:bg-gray-700 dark:text-gray-300"
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
>
|
>
|
||||||
<CloudArrowDownIcon className="hover:scale-110 w-5 h-5 text-current"></CloudArrowDownIcon>
|
<CloudArrowDownIcon className="hover:scale-110 w-5 h-5 text-current"></CloudArrowDownIcon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
className="bg-gray-100 shadow rounded w-1/2 text-gray-700 hover:drop-shadow-lg px-2 py-2 flex justify-between items-center border border-gray-300"
|
className="bg-gray-100 shadow rounded w-1/2 text-gray-700 hover:drop-shadow-lg px-2 py-2 flex justify-between items-center border border-gray-300"
|
||||||
>
|
>
|
||||||
<div className="flex gap-2 text-current items-center w-full mr-2">
|
<div className="flex gap-2 text-current items-center w-full mr-2">
|
||||||
{" "}
|
{" "}
|
||||||
{fileType === "image" ? (
|
{fileType === "image" ? (
|
||||||
<img
|
<img
|
||||||
src={`data:image/png;base64,${content}`}
|
src={`data:image/png;base64,${content}`}
|
||||||
alt=""
|
alt=""
|
||||||
className="w-8 h-8"
|
className="w-8 h-8"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<DocumentIcon className="w-8 h-8" />
|
<DocumentIcon className="w-8 h-8" />
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col items-start">
|
<div className="flex flex-col items-start">
|
||||||
{" "}
|
{" "}
|
||||||
<div className="truncate text-sm text-current">{fileName}</div>
|
<div className="truncate text-sm text-current">{fileName}</div>
|
||||||
<div className="truncate text-xs text-gray-500">{fileType}</div>
|
<div className="truncate text-xs text-gray-500">{fileType}</div>
|
||||||
</div>
|
</div>
|
||||||
<CloudArrowDownIcon className="w-6 h-6 text-current ml-auto" />
|
<CloudArrowDownIcon className="w-6 h-6 text-current ml-auto" />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -13,173 +13,164 @@ import { checkCode } from "../../controllers/API";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
export default function CodeAreaModal({
|
export default function CodeAreaModal({
|
||||||
value,
|
value,
|
||||||
setValue,
|
setValue,
|
||||||
}: {
|
}: {
|
||||||
setValue: (value: string) => void;
|
setValue: (value: string) => void;
|
||||||
value: string;
|
value: string;
|
||||||
}) {
|
}) {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
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) {
|
setOpen(x);
|
||||||
setOpen(x);
|
if (x === false) {
|
||||||
if (x === false) {
|
setTimeout(() => {
|
||||||
setTimeout(() => {
|
closePopUp();
|
||||||
closePopUp();
|
}, 300);
|
||||||
}, 300);
|
}
|
||||||
}
|
}
|
||||||
}
|
return (
|
||||||
return (
|
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
<Dialog
|
||||||
<Dialog
|
as="div"
|
||||||
as="div"
|
className="relative z-10"
|
||||||
className="relative z-10"
|
onClose={setModalOpen}
|
||||||
onClose={setModalOpen}
|
initialFocus={ref}
|
||||||
initialFocus={ref}
|
>
|
||||||
>
|
<Transition.Child
|
||||||
<Transition.Child
|
as={Fragment}
|
||||||
as={Fragment}
|
enter="ease-out duration-300"
|
||||||
enter="ease-out duration-300"
|
enterFrom="opacity-0"
|
||||||
enterFrom="opacity-0"
|
enterTo="opacity-100"
|
||||||
enterTo="opacity-100"
|
leave="ease-in duration-200"
|
||||||
leave="ease-in duration-200"
|
leaveFrom="opacity-100"
|
||||||
leaveFrom="opacity-100"
|
leaveTo="opacity-0"
|
||||||
leaveTo="opacity-0"
|
>
|
||||||
>
|
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
||||||
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
</Transition.Child>
|
||||||
</Transition.Child>
|
|
||||||
|
|
||||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
<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">
|
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
<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">
|
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-md text-gray-400 hover:text-gray-500"
|
className="rounded-md text-gray-400 hover:text-gray-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
<div className="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="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">
|
<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">
|
||||||
<CommandLineIcon
|
<CommandLineIcon
|
||||||
className="h-6 w-6 text-blue-600"
|
className="h-6 w-6 text-blue-600"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
||||||
>
|
>
|
||||||
Edit Code
|
Edit Code
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
</div>
|
</div>
|
||||||
</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="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="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">
|
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full h-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
||||||
<AceEditor
|
<AceEditor
|
||||||
value={code}
|
value={code}
|
||||||
mode="python"
|
mode="python"
|
||||||
highlightActiveLine={true}
|
highlightActiveLine={true}
|
||||||
showPrintMargin={false}
|
showPrintMargin={false}
|
||||||
fontSize={14}
|
fontSize={14}
|
||||||
showGutter
|
showGutter
|
||||||
enableLiveAutocompletion
|
enableLiveAutocompletion
|
||||||
theme={dark ? "twilight" : "github"}
|
theme={dark ? "twilight" : "github"}
|
||||||
name="CodeEditor"
|
name="CodeEditor"
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setCode(value);
|
setCode(value);
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
className="h-full w-full rounded-lg"
|
||||||
setDisableCopyPaste(false)
|
/>
|
||||||
}}
|
</div>
|
||||||
onFocus={() => {
|
</div>
|
||||||
setDisableCopyPaste(true)
|
</div>
|
||||||
}}
|
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
|
||||||
className="h-full w-full rounded-lg"
|
<button
|
||||||
/>
|
type="button"
|
||||||
</div>
|
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"
|
||||||
</div>
|
onClick={() => {
|
||||||
</div>
|
checkCode(code)
|
||||||
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
|
.then((apiReturn) => {
|
||||||
<button
|
if (apiReturn.data) {
|
||||||
type="button"
|
let importsErrors = apiReturn.data.imports.errors;
|
||||||
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"
|
let funcErrors = apiReturn.data.function.errors;
|
||||||
onClick={() => {
|
if (
|
||||||
checkCode(code)
|
funcErrors.length === 0 &&
|
||||||
.then((apiReturn) => {
|
importsErrors.length === 0
|
||||||
console.log(apiReturn);
|
) {
|
||||||
if (apiReturn.data) {
|
setSuccessData({
|
||||||
console.log(apiReturn.data);
|
title: "Code is ready to run",
|
||||||
let importsErrors = apiReturn.data.imports.errors;
|
});
|
||||||
let funcErrors = apiReturn.data.function.errors;
|
setModalOpen(false);
|
||||||
if (
|
setValue(code);
|
||||||
funcErrors.length === 0 &&
|
} else {
|
||||||
importsErrors.length === 0
|
if (funcErrors.length !== 0) {
|
||||||
) {
|
setErrorData({
|
||||||
setSuccessData({
|
title: "There is an error in your function",
|
||||||
title: "Code is ready to run",
|
list: funcErrors,
|
||||||
});
|
});
|
||||||
setModalOpen(false);
|
}
|
||||||
setValue(code);
|
if (importsErrors.length !== 0) {
|
||||||
} else {
|
setErrorData({
|
||||||
if (funcErrors.length !== 0) {
|
title: "There is an error in your imports",
|
||||||
setErrorData({
|
list: importsErrors,
|
||||||
title: "There is an error in your function",
|
});
|
||||||
list: funcErrors,
|
}
|
||||||
});
|
}
|
||||||
}
|
} else {
|
||||||
if (importsErrors.length !== 0) {
|
setErrorData({
|
||||||
setErrorData({
|
title: "Something went wrong, please try again",
|
||||||
title: "There is an error in your imports",
|
});
|
||||||
list: importsErrors,
|
}
|
||||||
});
|
})
|
||||||
}
|
.catch((_) =>
|
||||||
}
|
setErrorData({
|
||||||
} else {
|
title:
|
||||||
setErrorData({
|
"There is something wrong with this code, please review it",
|
||||||
title: "Something went wrong, please try again",
|
})
|
||||||
});
|
);
|
||||||
}
|
}}
|
||||||
})
|
>
|
||||||
.catch((_) =>
|
Check & Save
|
||||||
setErrorData({
|
</button>
|
||||||
title:
|
</div>
|
||||||
"There is something wrong with this code, please review it",
|
</div>
|
||||||
})
|
</Dialog.Panel>
|
||||||
);
|
</Transition.Child>
|
||||||
}}
|
</div>
|
||||||
>
|
</div>
|
||||||
Check & Save
|
</Dialog>
|
||||||
</button>
|
</Transition.Root>
|
||||||
</div>
|
);
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition.Root>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import {
|
import {
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
ArrowDownTrayIcon,
|
ArrowDownTrayIcon,
|
||||||
DocumentDuplicateIcon,
|
DocumentDuplicateIcon,
|
||||||
ComputerDesktopIcon,
|
ComputerDesktopIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { Fragment, useContext, useRef, useState } from "react";
|
import { Fragment, useContext, useRef, useState } from "react";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
|
|
@ -12,187 +12,169 @@ import { TabsContext } from "../../contexts/tabsContext";
|
||||||
import { removeApiKeys } from "../../utils";
|
import { removeApiKeys } from "../../utils";
|
||||||
|
|
||||||
export default function ExportModal() {
|
export default function ExportModal() {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
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) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
closePopUp();
|
closePopUp();
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const [checked, setChecked] = useState(true);
|
const [checked, setChecked] = useState(true);
|
||||||
const [name, setName] = useState(flows[tabIndex].name);
|
const [name, setName] = useState(flows[tabIndex].name);
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||||
<Dialog
|
<Dialog
|
||||||
as="div"
|
as="div"
|
||||||
className="relative z-10"
|
className="relative z-10"
|
||||||
onClose={setModalOpen}
|
onClose={setModalOpen}
|
||||||
initialFocus={ref}
|
initialFocus={ref}
|
||||||
>
|
>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
<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">
|
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
<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">
|
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-md text-gray-400 hover:text-gray-500"
|
className="rounded-md text-gray-400 hover:text-gray-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
<div className="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="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">
|
<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">
|
||||||
<ArrowDownTrayIcon
|
<ArrowDownTrayIcon
|
||||||
className="h-6 w-6 text-blue-600"
|
className="h-6 w-6 text-blue-600"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
||||||
>
|
>
|
||||||
Export as
|
Export as
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-16 flex flex-col items-start justify-start h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-16">
|
<div className="pt-16 flex flex-col items-start justify-start h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-16">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label
|
<label
|
||||||
htmlFor="name"
|
htmlFor="name"
|
||||||
className="block mb-2 font-medium text-gray-700 dark:text-white"
|
className="block mb-2 font-medium text-gray-700 dark:text-white"
|
||||||
>
|
>
|
||||||
Name
|
Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (event.target.value != "") {
|
if (event.target.value != "") {
|
||||||
let newFlow = flows[tabIndex];
|
let newFlow = flows[tabIndex];
|
||||||
newFlow.name = event.target.value;
|
newFlow.name = event.target.value;
|
||||||
setName(event.target.value);
|
setName(event.target.value);
|
||||||
updateFlow(newFlow);
|
updateFlow(newFlow);
|
||||||
} else {
|
} else {
|
||||||
setName(event.target.value);
|
setName(event.target.value);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="name"
|
||||||
value={name ?? null}
|
value={name ?? null}
|
||||||
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);
|
</div>
|
||||||
}}
|
<div className="w-full">
|
||||||
onFocus={() => {
|
<label
|
||||||
setDisableCopyPaste(true);
|
htmlFor="description"
|
||||||
}}
|
className="block mb-2 font-medium text-gray-700 dark:text-white"
|
||||||
/>
|
>
|
||||||
</div>
|
Description{" "}
|
||||||
<div className="w-full">
|
<span className="text-gray-400 text-sm">
|
||||||
<label
|
{" "}
|
||||||
htmlFor="description"
|
(optional)
|
||||||
className="block mb-2 font-medium text-gray-700 dark:text-white"
|
</span>
|
||||||
>
|
</label>
|
||||||
Description{" "}
|
<textarea
|
||||||
<span className="text-gray-400 text-sm">
|
name="description"
|
||||||
{" "}
|
id="description"
|
||||||
(optional)
|
onChange={(event) => {
|
||||||
</span>
|
let newFlow = flows[tabIndex];
|
||||||
</label>
|
newFlow.description = event.target.value;
|
||||||
<textarea
|
updateFlow(newFlow);
|
||||||
onBlur={() => {
|
}}
|
||||||
setDisableCopyPaste(false);
|
value={flows[tabIndex].description ?? null}
|
||||||
}}
|
placeholder="Flow description"
|
||||||
onFocus={() => {
|
rows={3}
|
||||||
setDisableCopyPaste(true);
|
className=" focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 text-gray-900 dark:text-gray-100 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"
|
||||||
}}
|
></textarea>
|
||||||
name="description"
|
</div>
|
||||||
id="description"
|
|
||||||
onChange={(event) => {
|
|
||||||
let newFlow = flows[tabIndex];
|
|
||||||
newFlow.description = event.target.value;
|
|
||||||
updateFlow(newFlow);
|
|
||||||
}}
|
|
||||||
value={flows[tabIndex].description ?? null}
|
|
||||||
placeholder="Flow description"
|
|
||||||
rows={3}
|
|
||||||
className=" focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 text-gray-900 dark:text-gray-100 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"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="checkbox" className="flex items-center">
|
<label htmlFor="checkbox" className="flex items-center">
|
||||||
<input
|
<input
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setChecked(event.target.checked);
|
setChecked(event.target.checked);
|
||||||
}}
|
}}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
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);
|
<span className="ml-2 font-medium text-gray-700 dark:text-white">
|
||||||
}}
|
Save with my API keys
|
||||||
onFocus={() => {
|
</span>
|
||||||
setDisableCopyPaste(true);
|
</label>
|
||||||
}}
|
</div>
|
||||||
/>
|
<div className="w-full flex justify-end">
|
||||||
<span className="ml-2 font-medium text-gray-700 dark:text-white">
|
<button
|
||||||
Save with my API keys
|
onClick={() => {
|
||||||
</span>
|
if (checked) downloadFlow(flows[tabIndex]);
|
||||||
</label>
|
else downloadFlow(removeApiKeys(flows[tabIndex]));
|
||||||
</div>
|
}}
|
||||||
<div className="w-full flex justify-end">
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||||
<button
|
>
|
||||||
onClick={() => {
|
Download Flow
|
||||||
if (checked) downloadFlow(flows[tabIndex]);
|
</button>
|
||||||
else downloadFlow(removeApiKeys(flows[tabIndex]));
|
</div>
|
||||||
}}
|
</div>
|
||||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
</div>
|
||||||
>
|
</Dialog.Panel>
|
||||||
Download Flow
|
</Transition.Child>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Dialog>
|
||||||
</div>
|
</Transition.Root>
|
||||||
</Dialog.Panel>
|
);
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition.Root>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -33,48 +33,47 @@ export default function ButtonBox({
|
||||||
let textHeight: number;
|
let textHeight: number;
|
||||||
let textWidth: number;
|
let textWidth: number;
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case "small":
|
case "small":
|
||||||
bigCircle = "h-12 w-12";
|
bigCircle = "h-12 w-12";
|
||||||
smallCircle = "h-8 w-8";
|
smallCircle = "h-8 w-8";
|
||||||
titleFontSize = "text-sm";
|
titleFontSize = "text-sm";
|
||||||
descriptionFontSize = "text-xs";
|
descriptionFontSize = "text-xs";
|
||||||
padding = "p-2 py-3";
|
padding = "p-2 py-3";
|
||||||
marginTop = "mt-2";
|
marginTop = "mt-2";
|
||||||
height = "h-36";
|
height = "h-36";
|
||||||
width = "w-32";
|
width = "w-32";
|
||||||
break;
|
break;
|
||||||
case "medium":
|
case "medium":
|
||||||
bigCircle = "h-16 w-16";
|
bigCircle = "h-16 w-16";
|
||||||
smallCircle = "h-12 w-12";
|
smallCircle = "h-12 w-12";
|
||||||
titleFontSize = "text-base";
|
titleFontSize = "text-base";
|
||||||
descriptionFontSize = "text-sm";
|
descriptionFontSize = "text-sm";
|
||||||
padding = "p-4 py-5";
|
padding = "p-4 py-5";
|
||||||
marginTop = "mt-3";
|
marginTop = "mt-3";
|
||||||
height = "h-44";
|
height = "h-44";
|
||||||
width = "w-36";
|
width = "w-36";
|
||||||
break;
|
break;
|
||||||
case "big":
|
case "big":
|
||||||
bigCircle = "h-20 w-20";
|
bigCircle = "h-20 w-20";
|
||||||
smallCircle = "h-16 w-16";
|
smallCircle = "h-16 w-16";
|
||||||
titleFontSize = "text-lg";
|
titleFontSize = "text-lg";
|
||||||
descriptionFontSize = "text-sm";
|
descriptionFontSize = "text-sm";
|
||||||
padding = "p-8 py-10";
|
padding = "p-8 py-10";
|
||||||
marginTop = "mt-6";
|
marginTop = "mt-6";
|
||||||
height = "h-56";
|
height = "h-56";
|
||||||
width = "w-44";
|
width = "w-44";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
bigCircle = "h-20 w-20";
|
bigCircle = "h-20 w-20";
|
||||||
smallCircle = "h-16 w-16";
|
smallCircle = "h-16 w-16";
|
||||||
titleFontSize = "text-lg";
|
titleFontSize = "text-lg";
|
||||||
descriptionFontSize = "text-sm";
|
descriptionFontSize = "text-sm";
|
||||||
padding = "p-8 py-10";
|
padding = "p-8 py-10";
|
||||||
marginTop = "mt-6";
|
marginTop = "mt-6";
|
||||||
height = "h-56";
|
height = "h-56";
|
||||||
width = "w-44";
|
width = "w-44";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button disabled={deactivate} onClick={onClick}>
|
<button disabled={deactivate} onClick={onClick}>
|
||||||
|
|
@ -97,16 +96,16 @@ export default function ButtonBox({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full mt-auto mb-auto">
|
<div className="w-full mt-auto mb-auto">
|
||||||
<h3
|
<h3
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"w-full font-semibold break-words text-white dark:text-white/80 truncate-multiline",
|
"w-full font-semibold break-words text-white dark:text-white/80 truncate-multiline",
|
||||||
titleFontSize,
|
titleFontSize,
|
||||||
marginTop
|
marginTop
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import {
|
import {
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
ArrowDownTrayIcon,
|
ArrowDownTrayIcon,
|
||||||
DocumentDuplicateIcon,
|
DocumentDuplicateIcon,
|
||||||
ComputerDesktopIcon,
|
ComputerDesktopIcon,
|
||||||
ArrowUpTrayIcon,
|
ArrowUpTrayIcon,
|
||||||
ArrowLeftIcon,
|
ArrowLeftIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} 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";
|
||||||
|
|
@ -19,214 +19,214 @@ import { FlowType } from "../../types/flow";
|
||||||
import { classNames, snakeToSpaces, toNormalCase } from "../../utils";
|
import { classNames, snakeToSpaces, toNormalCase } from "../../utils";
|
||||||
|
|
||||||
export default function ImportModal() {
|
export default function ImportModal() {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
const [showExamples, setShowExamples] = useState(false);
|
const [showExamples, setShowExamples] = useState(false);
|
||||||
const [loadingExamples, setLoadingExamples] = useState(false);
|
const [loadingExamples, setLoadingExamples] = useState(false);
|
||||||
const [examples, setExamples] = useState<FlowType[]>([]);
|
const [examples, setExamples] = useState<FlowType[]>([]);
|
||||||
const { uploadFlow, addFlow } = useContext(TabsContext);
|
const { uploadFlow, addFlow } = useContext(TabsContext);
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
setOpen(x);
|
setOpen(x);
|
||||||
if (x === false) {
|
if (x === false) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
closePopUp();
|
closePopUp();
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleExamples() {
|
function handleExamples() {
|
||||||
setLoadingExamples(true);
|
setLoadingExamples(true);
|
||||||
getExamples()
|
getExamples()
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
setLoadingExamples(false);
|
setLoadingExamples(false);
|
||||||
setExamples(result);
|
setExamples(result);
|
||||||
})
|
})
|
||||||
.catch((error) =>
|
.catch((error) =>
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "there was an error loading examples, please try again",
|
title: "there was an error loading examples, please try again",
|
||||||
list: [error.message],
|
list: [error.message],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||||
<Dialog
|
<Dialog
|
||||||
as="div"
|
as="div"
|
||||||
className="relative z-10"
|
className="relative z-10"
|
||||||
onClose={setModalOpen}
|
onClose={setModalOpen}
|
||||||
initialFocus={ref}
|
initialFocus={ref}
|
||||||
>
|
>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
<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">
|
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[776px]">
|
<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-[776px]">
|
||||||
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-md text-gray-400 hover:text-gray-500"
|
className="rounded-md text-gray-400 hover:text-gray-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{showExamples && (
|
{showExamples && (
|
||||||
<>
|
<>
|
||||||
<div className="z-50 absolute top-2 left-0 hidden pt-4 pl-4 sm:block">
|
<div className="z-50 absolute top-2 left-0 hidden pt-4 pl-4 sm:block">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowExamples(false);
|
setShowExamples(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
<ArrowLeftIcon className="h-6 w-6" aria-hidden="true" />
|
<ArrowLeftIcon className="h-6 w-6" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
<div className="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="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">
|
<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">
|
||||||
<ArrowUpTrayIcon
|
<ArrowUpTrayIcon
|
||||||
className="h-6 w-6 text-blue-600"
|
className="h-6 w-6 text-blue-600"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
||||||
>
|
>
|
||||||
{showExamples ? "Select an example" : "Import from"}
|
{showExamples ? "Select an example" : "Import from"}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"h-full w-full bg-gray-200 dark:bg-gray-900 gap-4 overflow-y-auto scrollbar-hide",
|
"h-full w-full bg-gray-200 dark:bg-gray-900 gap-4 overflow-y-auto scrollbar-hide",
|
||||||
showExamples && !loadingExamples
|
showExamples && !loadingExamples
|
||||||
? "flex flex-row start justify-start items-start p-9 flex-wrap overflow-auto"
|
? "flex flex-row start justify-start items-start p-9 flex-wrap overflow-auto"
|
||||||
: "flex flex-row justify-center items-center p-4"
|
: "flex flex-row justify-center items-center p-4"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!showExamples && (
|
{!showExamples && (
|
||||||
<div className="flex h-full w-full justify-evenly items-center">
|
<div className="flex h-full w-full justify-evenly items-center">
|
||||||
<ButtonBox
|
<ButtonBox
|
||||||
size="big"
|
size="big"
|
||||||
bgColor="bg-emerald-500 dark:bg-emerald-500/75"
|
bgColor="bg-emerald-500 dark:bg-emerald-500/75"
|
||||||
description="Prebuilt Examples"
|
description="Prebuilt Examples"
|
||||||
icon={
|
icon={
|
||||||
<DocumentDuplicateIcon className="h-10 w-10 flex-shrink-0" />
|
<DocumentDuplicateIcon className="h-10 w-10 flex-shrink-0" />
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowExamples(true);
|
setShowExamples(true);
|
||||||
handleExamples();
|
handleExamples();
|
||||||
}}
|
}}
|
||||||
textColor="text-emerald-500 dark:text-emerald-500/75"
|
textColor="text-emerald-500 dark:text-emerald-500/75"
|
||||||
title="Examples"
|
title="Examples"
|
||||||
></ButtonBox>
|
></ButtonBox>
|
||||||
<ButtonBox
|
<ButtonBox
|
||||||
size="big"
|
size="big"
|
||||||
bgColor="bg-blue-500 dark:bg-blue-500/75"
|
bgColor="bg-blue-500 dark:bg-blue-500/75"
|
||||||
description="Import from Local"
|
description="Import from Local"
|
||||||
icon={
|
icon={
|
||||||
<ComputerDesktopIcon className="h-10 w-10 flex-shrink-0" />
|
<ComputerDesktopIcon className="h-10 w-10 flex-shrink-0" />
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
uploadFlow();
|
uploadFlow();
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
textColor="text-blue-500 dark:text-blue-500/75"
|
textColor="text-blue-500 dark:text-blue-500/75"
|
||||||
title="Local File"
|
title="Local File"
|
||||||
></ButtonBox>
|
></ButtonBox>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{showExamples && loadingExamples && (
|
{showExamples && loadingExamples && (
|
||||||
<div className="flex align-middle justify-center items-center">
|
<div className="flex align-middle justify-center items-center">
|
||||||
<LoadingComponent remSize={30} />
|
<LoadingComponent remSize={30} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{showExamples &&
|
{showExamples &&
|
||||||
!loadingExamples &&
|
!loadingExamples &&
|
||||||
examples.map((example, index) => {
|
examples.map((example, index) => {
|
||||||
return (
|
return (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
{" "}
|
{" "}
|
||||||
<ButtonBox
|
<ButtonBox
|
||||||
size="small"
|
size="small"
|
||||||
bgColor="bg-emerald-500 dark:bg-emerald-500/75"
|
bgColor="bg-emerald-500 dark:bg-emerald-500/75"
|
||||||
description={
|
description={
|
||||||
example.description ?? "Prebuilt Examples"
|
example.description ?? "Prebuilt Examples"
|
||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
<DocumentDuplicateIcon className="h-6 w-6 flex-shrink-0" />
|
<DocumentDuplicateIcon className="h-6 w-6 flex-shrink-0" />
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
addFlow(example);
|
addFlow(example);
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
textColor="text-emerald-500 dark:text-emerald-500/75"
|
textColor="text-emerald-500 dark:text-emerald-500/75"
|
||||||
title={example.name}
|
title={example.name}
|
||||||
></ButtonBox>
|
></ButtonBox>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white dark:bg-gray-800 w-full h-20 flex items-center justify-center">
|
<div className="bg-white dark:bg-gray-800 w-full h-20 flex items-center justify-center">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/logspace-ai/langflow_examples"
|
href="https://github.com/logspace-ai/langflow_examples"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="flex items-center justify-center text-gray-600 dark:text-gray-300"
|
className="flex items-center justify-center text-gray-600 dark:text-gray-300"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
width="24"
|
width="24"
|
||||||
viewBox="0 0 98 96"
|
viewBox="0 0 98 96"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
clip-rule="evenodd"
|
clip-rule="evenodd"
|
||||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span className="ml-2 ">LangFlow Examples</span>
|
<span className="ml-2 ">LangFlow Examples</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,149 +6,148 @@ import { darkContext } from "../../contexts/darkContext";
|
||||||
import { checkPrompt } from "../../controllers/API";
|
import { checkPrompt } from "../../controllers/API";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
export default function PromptAreaModal({
|
export default function PromptAreaModal({
|
||||||
value,
|
value,
|
||||||
setValue,
|
setValue,
|
||||||
}: {
|
}: {
|
||||||
setValue: (value: string) => void;
|
setValue: (value: string) => void;
|
||||||
value: string;
|
value: string;
|
||||||
}) {
|
}) {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
const [myValue, setMyValue] = useState(value);
|
const [myValue, setMyValue] = useState(value);
|
||||||
const { dark } = useContext(darkContext);
|
const { dark } = useContext(darkContext);
|
||||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
setOpen(x);
|
setOpen(x);
|
||||||
if (x === false) {
|
if (x === false) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
closePopUp();
|
closePopUp();
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||||
<Dialog
|
<Dialog
|
||||||
as="div"
|
as="div"
|
||||||
className="relative z-10"
|
className="relative z-10"
|
||||||
onClose={setModalOpen}
|
onClose={setModalOpen}
|
||||||
initialFocus={ref}
|
initialFocus={ref}
|
||||||
>
|
>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
<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">
|
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
<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">
|
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-md text-gray-400 hover:text-gray-500"
|
className="rounded-md text-gray-400 hover:text-gray-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
<div className="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="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">
|
<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
|
<DocumentTextIcon
|
||||||
className="h-6 w-6 text-blue-600"
|
className="h-6 w-6 text-blue-600"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
||||||
>
|
>
|
||||||
Edit Prompt
|
Edit Prompt
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
</div>
|
</div>
|
||||||
</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="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="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">
|
<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
|
<textarea
|
||||||
ref={ref}
|
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"
|
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}
|
value={myValue}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setMyValue(e.target.value);
|
setMyValue(e.target.value);
|
||||||
setValue(e.target.value);
|
setValue(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
|
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
|
||||||
<button
|
<button
|
||||||
type="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"
|
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={() => {
|
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;
|
if (inputVariables.length === 0) {
|
||||||
if (inputVariables.length === 0) {
|
setErrorData({
|
||||||
setErrorData({
|
title:
|
||||||
title:
|
"The template you are attempting to use does not contain any variables for data entry.",
|
||||||
"The template you are attempting to use does not contain any variables for data entry.",
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
setSuccessData({
|
||||||
setSuccessData({
|
title: "Prompt is ready",
|
||||||
title: "Prompt is ready",
|
});
|
||||||
});
|
setModalOpen(false);
|
||||||
setModalOpen(false);
|
setValue(myValue);
|
||||||
setValue(myValue);
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
setErrorData({
|
||||||
setErrorData({
|
title: "Something went wrong, please try again",
|
||||||
title: "Something went wrong, please try again",
|
});
|
||||||
});
|
}
|
||||||
}
|
})
|
||||||
})
|
.catch((error) => {
|
||||||
.catch((error) => {
|
return setErrorData({
|
||||||
return setErrorData({
|
title:
|
||||||
title:
|
"There is something wrong with this prompt, please review it",
|
||||||
"There is something wrong with this prompt, please review it",
|
list: [error.response.data.detail],
|
||||||
list: [error.response.data.detail],
|
});
|
||||||
});
|
});
|
||||||
});
|
}}
|
||||||
}}
|
>
|
||||||
>
|
Check & Save
|
||||||
Check & Save
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Dialog.Panel>
|
||||||
</Dialog.Panel>
|
</Transition.Child>
|
||||||
</Transition.Child>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Dialog>
|
||||||
</Dialog>
|
</Transition.Root>
|
||||||
</Transition.Root>
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,123 +1,123 @@
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import {
|
import {
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
ClipboardDocumentListIcon,
|
ClipboardDocumentListIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} 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";
|
||||||
|
|
||||||
export default function TextAreaModal({
|
export default function TextAreaModal({
|
||||||
value,
|
value,
|
||||||
setValue,
|
setValue,
|
||||||
}: {
|
}: {
|
||||||
setValue: (value: string) => void;
|
setValue: (value: string) => void;
|
||||||
value: string | string[];
|
value: string | string[];
|
||||||
}) {
|
}) {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
const [myValue, setMyValue] = useState(value);
|
const [myValue, setMyValue] = useState(value);
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
setOpen(x);
|
setOpen(x);
|
||||||
if (x === false) {
|
if (x === false) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
closePopUp();
|
closePopUp();
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||||
<Dialog
|
<Dialog
|
||||||
as="div"
|
as="div"
|
||||||
className="relative z-10"
|
className="relative z-10"
|
||||||
onClose={setModalOpen}
|
onClose={setModalOpen}
|
||||||
initialFocus={ref}
|
initialFocus={ref}
|
||||||
>
|
>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
<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">
|
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
<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">
|
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-md text-gray-400 hover:text-gray-500"
|
className="rounded-md text-gray-400 hover:text-gray-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
<div className="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="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">
|
<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">
|
||||||
<ClipboardDocumentListIcon
|
<ClipboardDocumentListIcon
|
||||||
className="h-6 w-6 text-blue-600"
|
className="h-6 w-6 text-blue-600"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
||||||
>
|
>
|
||||||
Edit text
|
Edit text
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
|
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
|
||||||
<div className="flex h-full w-full">
|
<div className="flex h-full w-full">
|
||||||
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
||||||
<textarea
|
<textarea
|
||||||
ref={ref}
|
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"
|
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}
|
value={myValue}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setMyValue(e.target.value);
|
setMyValue(e.target.value);
|
||||||
setValue(e.target.value);
|
setValue(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
|
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
|
||||||
<button
|
<button
|
||||||
type="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"
|
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={() => {
|
onClick={() => {
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Finish editing
|
Finish editing
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,33 @@
|
||||||
import { ConnectionLineComponentProps } from "reactflow";
|
import { ConnectionLineComponentProps } from "reactflow";
|
||||||
|
|
||||||
const ConnectionLineComponent = ({
|
const ConnectionLineComponent = ({
|
||||||
fromX,
|
fromX,
|
||||||
fromY,
|
fromY,
|
||||||
toX,
|
toX,
|
||||||
toY,
|
toY,
|
||||||
connectionLineStyle = {}, // provide a default value for connectionLineStyle
|
connectionLineStyle = {}, // provide a default value for connectionLineStyle
|
||||||
}: ConnectionLineComponentProps) => {
|
}: ConnectionLineComponentProps) => {
|
||||||
return (
|
return (
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#222"
|
stroke="#222"
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
className="animated dark:stroke-gray-400"
|
className="animated dark:stroke-gray-400"
|
||||||
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
|
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
|
||||||
style={connectionLineStyle}
|
style={connectionLineStyle}
|
||||||
/>
|
/>
|
||||||
<circle
|
<circle
|
||||||
cx={toX}
|
cx={toX}
|
||||||
cy={toY}
|
cy={toY}
|
||||||
fill="#fff"
|
fill="#fff"
|
||||||
r={3}
|
r={3}
|
||||||
stroke="#222"
|
stroke="#222"
|
||||||
className="dark:stroke-gray-400 dark:fill-gray-800"
|
className="dark:stroke-gray-400 dark:fill-gray-800"
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ConnectionLineComponent;
|
export default ConnectionLineComponent;
|
||||||
|
|
|
||||||
|
|
@ -3,42 +3,42 @@ import { Disclosure } from "@headlessui/react";
|
||||||
import { DisclosureComponentType } from "../../../../types/components";
|
import { DisclosureComponentType } from "../../../../types/components";
|
||||||
|
|
||||||
export default function DisclosureComponent({
|
export default function DisclosureComponent({
|
||||||
button: { title, Icon, buttons = [] },
|
button: { title, Icon, buttons = [] },
|
||||||
children,
|
children,
|
||||||
}: DisclosureComponentType) {
|
}: DisclosureComponentType) {
|
||||||
return (
|
return (
|
||||||
<Disclosure as="div" key={title}>
|
<Disclosure as="div" key={title}>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<Disclosure.Button className="select-none bg-gray-50 dark:bg-gray-700/60 dark:border-y-gray-600 w-full flex justify-between items-center -mt-px px-3 py-2 border-y border-y-gray-200">
|
<Disclosure.Button className="select-none bg-gray-50 dark:bg-gray-700/60 dark:border-y-gray-600 w-full flex justify-between items-center -mt-px px-3 py-2 border-y border-y-gray-200">
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<Icon className="w-6 text-gray-800 dark:text-white/80" />
|
<Icon className="w-6 text-gray-800 dark:text-white/80" />
|
||||||
<span className="flex items-center text-sm text-gray-800 dark:text-white/80 font-medium">
|
<span className="flex items-center text-sm text-gray-800 dark:text-white/80 font-medium">
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{buttons.map((x, index) => (
|
{buttons.map((x, index) => (
|
||||||
<button key={index} onClick={x.onClick}>
|
<button key={index} onClick={x.onClick}>
|
||||||
{x.Icon}
|
{x.Icon}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
<div>
|
<div>
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
className={`${
|
className={`${
|
||||||
open ? "rotate-90 transform" : ""
|
open ? "rotate-90 transform" : ""
|
||||||
} h-4 w-4 text-gray-800 dark:text-white`}
|
} h-4 w-4 text-gray-800 dark:text-white`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
</div>
|
</div>
|
||||||
<Disclosure.Panel as="div" className="-mt-px">
|
<Disclosure.Panel as="div" className="-mt-px">
|
||||||
{children}
|
{children}
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,62 +6,62 @@ import { typesContext } from "../../../../contexts/typesContext";
|
||||||
import { APIClassType, APIObjectType } from "../../../../types/api";
|
import { APIClassType, APIObjectType } from "../../../../types/api";
|
||||||
|
|
||||||
export default function ExtraSidebar() {
|
export default function ExtraSidebar() {
|
||||||
const { data } = useContext(typesContext);
|
const { data } = useContext(typesContext);
|
||||||
|
|
||||||
function onDragStart(
|
function onDragStart(
|
||||||
event: React.DragEvent<any>,
|
event: React.DragEvent<any>,
|
||||||
data: { type: string; node?: APIClassType }
|
data: { type: string; node?: APIClassType }
|
||||||
) {
|
) {
|
||||||
//start drag event
|
//start drag event
|
||||||
event.dataTransfer.effectAllowed = "move";
|
event.dataTransfer.effectAllowed = "move";
|
||||||
event.dataTransfer.setData("json", JSON.stringify(data));
|
event.dataTransfer.setData("json", JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-1 w-full">
|
<div className="mt-1 w-full">
|
||||||
{Object.keys(data)
|
{Object.keys(data)
|
||||||
.sort()
|
.sort()
|
||||||
.map((d: keyof APIObjectType, i) => (
|
.map((d: keyof APIObjectType, i) => (
|
||||||
<DisclosureComponent
|
<DisclosureComponent
|
||||||
key={i}
|
key={i}
|
||||||
button={{
|
button={{
|
||||||
title: nodeNames[d] ?? nodeNames.unknown,
|
title: nodeNames[d] ?? nodeNames.unknown,
|
||||||
Icon: nodeIcons[d] ?? nodeIcons.unknown,
|
Icon: nodeIcons[d] ?? nodeIcons.unknown,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="p-2 flex flex-col gap-2">
|
<div className="p-2 flex flex-col gap-2">
|
||||||
{Object.keys(data[d])
|
{Object.keys(data[d])
|
||||||
.sort()
|
.sort()
|
||||||
.map((t: string, k) => (
|
.map((t: string, k) => (
|
||||||
<div key={k}>
|
<div key={k}>
|
||||||
<div
|
<div
|
||||||
draggable
|
draggable
|
||||||
className={" cursor-grab border-l-8 rounded-l-md"}
|
className={" cursor-grab border-l-8 rounded-l-md"}
|
||||||
style={{
|
style={{
|
||||||
borderLeftColor: nodeColors[d] ?? nodeColors.unknown,
|
borderLeftColor: nodeColors[d] ?? nodeColors.unknown,
|
||||||
}}
|
}}
|
||||||
onDragStart={(event) =>
|
onDragStart={(event) =>
|
||||||
onDragStart(event, {
|
onDragStart(event, {
|
||||||
type: t,
|
type: t,
|
||||||
node: data[d][t],
|
node: data[d][t],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="flex w-full justify-between text-sm px-3 py-1 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
|
<div className="flex w-full justify-between text-sm px-3 py-1 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
|
||||||
<span className="text-black dark:text-white w-36 pr-1 truncate text-xs">
|
<span className="text-black dark:text-white w-36 pr-1 truncate text-xs">
|
||||||
{t}
|
{t}
|
||||||
</span>
|
</span>
|
||||||
<Bars2Icon className="w-4 h-6 text-gray-400 dark:text-gray-600" />
|
<Bars2Icon className="w-4 h-6 text-gray-400 dark:text-gray-600" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{Object.keys(data[d]).length === 0 && (
|
{Object.keys(data[d]).length === 0 && (
|
||||||
<div className="text-gray-400 text-center">Coming soon</div>
|
<div className="text-gray-400 text-center">Coming soon</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DisclosureComponent>
|
</DisclosureComponent>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue