flow state refactor (#1358)

this pull request refactors the flow state preventing bugs and
unexpected behaviours
This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-01-19 15:33:30 -03:00 committed by GitHub
commit cc31658a37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
109 changed files with 3363 additions and 11171 deletions

View file

@ -45,6 +45,13 @@ run_frontend:
@-kill -9 `lsof -t -i:3000`
cd src/frontend && npm start
tests_frontend:
ifeq ($(UI), true)
cd src/frontend && ./run-tests.sh --ui
else
cd src/frontend && ./run-tests.sh
endif
run_cli:
poetry run langflow run --path src/frontend/build

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "langflow"
version = "0.6.5a8"
version = "0.6.5a9"
description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"]
maintainers = [

View file

@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi import Request, Response, APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlmodel import Session
@ -16,6 +16,7 @@ router = APIRouter(tags=["Login"])
@router.post("/login", response_model=Token)
async def login_to_get_access_token(
response: Response,
form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_session),
# _: Session = Depends(get_current_active_user)
@ -31,7 +32,10 @@ async def login_to_get_access_token(
) from exc
if user:
return create_user_tokens(user_id=user.id, db=db, update_last_login=True)
tokens = create_user_tokens(user_id=user.id, db=db, update_last_login=True)
response.set_cookie("refresh_token_lf", tokens["refresh_token"], httponly=True, secure=True, samesite="strict")
response.set_cookie("access_token_lf", tokens["access_token"], httponly=False, secure=True, samesite="strict")
return tokens
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@ -41,9 +45,13 @@ async def login_to_get_access_token(
@router.get("/auto_login")
async def auto_login(db: Session = Depends(get_session), settings_service=Depends(get_settings_service)):
async def auto_login(
response: Response, db: Session = Depends(get_session), settings_service=Depends(get_settings_service)
):
if settings_service.auth_settings.AUTO_LOGIN:
return create_user_longterm_token(db)
tokens = create_user_longterm_token(db)
response.set_cookie("access_token_lf", tokens["access_token"], httponly=False, secure=True, samesite="strict")
return tokens
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@ -55,12 +63,23 @@ async def auto_login(db: Session = Depends(get_session), settings_service=Depend
@router.post("/refresh")
async def refresh_token(token: str):
async def refresh_token(request: Request, response: Response):
token = request.cookies.get("refresh_token_lf")
if token:
return create_refresh_token(token)
tokens = create_refresh_token(token)
response.set_cookie("refresh_token_lf", tokens["refresh_token"], httponly=True, secure=True, samesite="strict")
response.set_cookie("access_token_lf", tokens["access_token"], httponly=False, secure=True, samesite="strict")
return tokens
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid refresh token",
headers={"WWW-Authenticate": "Bearer"},
)
@router.post("/logout")
async def logout(response: Response):
response.delete_cookie("refresh_token_lf")
response.delete_cookie("access_token_lf")
return {"message": "Logout successful"}

View file

@ -2,19 +2,20 @@ from langflow import CustomComponent
from langchain.embeddings.base import Embeddings
from langchain_community.embeddings import AzureOpenAIEmbeddings
class AzureOpenAIEmbeddingsComponent(CustomComponent):
display_name: str = "AzureOpenAIEmbeddings"
description: str = "Embeddings model from Azure OpenAI."
documentation: str = "https://python.langchain.com/docs/integrations/text_embedding/azureopenai"
beta = False
API_VERSION_OPTIONS = [
"2022-12-01",
"2023-03-15-preview",
"2023-05-15",
"2023-06-01-preview",
"2023-07-01-preview",
"2023-08-01-preview"
"2023-08-01-preview",
]
def build_config(self):
@ -39,10 +40,9 @@ class AzureOpenAIEmbeddingsComponent(CustomComponent):
"required": True,
"password": True,
},
"code": {
"show": False
},
"code": {"show": False},
}
def build(
self,
azure_endpoint: str,
@ -52,13 +52,13 @@ class AzureOpenAIEmbeddingsComponent(CustomComponent):
) -> Embeddings:
try:
embeddings = AzureOpenAIEmbeddings(
azure_endpoint = azure_endpoint,
deployment = azure_deployment,
openai_api_version = api_version,
openai_api_key = api_key,
azure_endpoint=azure_endpoint,
deployment=azure_deployment,
openai_api_version=api_version,
openai_api_key=api_key,
)
except Exception as e:
raise ValueError("Could not connect to AzureOpenAIEmbeddings API.") from e
return embeddings

View file

@ -4,6 +4,7 @@ from langflow import CustomComponent
from langchain.embeddings.base import Embeddings
from langchain_community.embeddings import OllamaEmbeddings
class OllamaEmbeddingsComponent(CustomComponent):
"""
A custom component for implementing an Embeddings Model using Ollama.
@ -23,7 +24,7 @@ class OllamaEmbeddingsComponent(CustomComponent):
"temperature": {"display_name": "Model Temperature"},
"code": {"show": False},
}
def build(
self,
model: str = "llama2",
@ -31,11 +32,7 @@ class OllamaEmbeddingsComponent(CustomComponent):
temperature: Optional[float] = None,
) -> Embeddings:
try:
output = OllamaEmbeddings(
model=model,
base_url=base_url,
temperature=temperature
) # type: ignore
output = OllamaEmbeddings(model=model, base_url=base_url, temperature=temperature) # type: ignore
except Exception as e:
raise ValueError("Could not connect to Ollama API.") from e
return output
return output

View file

@ -53,11 +53,7 @@ class AzureChatOpenAIComponent(CustomComponent):
"required": True,
"advanced": True,
},
"api_key": {
"display_name": "API Key",
"required": True,
"password": True
},
"api_key": {"display_name": "API Key", "required": True, "password": True},
"temperature": {
"display_name": "Temperature",
"value": 0.7,
@ -72,10 +68,9 @@ class AzureChatOpenAIComponent(CustomComponent):
"advanced": True,
"info": "Maximum number of tokens to generate.",
},
"code": {
"show": False
},
"code": {"show": False},
}
def build(
self,
model: str,

View file

@ -150,10 +150,9 @@ class OllamaLLM(CustomComponent):
"top_k": top_k,
"top_p": top_p,
}
# None Value remove
llm_params = {k: v for k, v in llm_params.items() if v is not None}
# None Value remove
llm_params = {k: v for k, v in llm_params.items() if v is not None}
try:
llm = Ollama(**llm_params)

View file

@ -15,29 +15,21 @@ class VectaraSelfQueryRetriverComponent(CustomComponent):
display_name: str = "Vectara Self Query Retriever for Vectara Vector Store"
description: str = "Implementation of Vectara Self Query Retriever"
documentation = (
"https://python.langchain.com/docs/integrations/retrievers/self_query/vectara_self_query"
)
documentation = "https://python.langchain.com/docs/integrations/retrievers/self_query/vectara_self_query"
beta = True
field_config = {
"code": {"show": True},
"vectorstore": {
"display_name": "Vector Store",
"info": "Input Vectara Vectore Store"
},
"llm": {
"display_name": "LLM",
"info": "For self query retriever"
},
"document_content_description":{
"display_name": "Document Content Description",
"vectorstore": {"display_name": "Vector Store", "info": "Input Vectara Vectore Store"},
"llm": {"display_name": "LLM", "info": "For self query retriever"},
"document_content_description": {
"display_name": "Document Content Description",
"info": "For self query retriever",
},
},
"metadata_field_info": {
"display_name": "Metadata Field Info",
"info": "Each metadata field info is a string in the form of key value pair dictionary containing additional search metadata.\nExample input: {\"name\":\"speech\",\"description\":\"what name of the speech\",\"type\":\"string or list[string]\"}.\nThe keys should remain constant(name, description, type)",
},
"display_name": "Metadata Field Info",
"info": 'Each metadata field info is a string in the form of key value pair dictionary containing additional search metadata.\nExample input: {"name":"speech","description":"what name of the speech","type":"string or list[string]"}.\nThe keys should remain constant(name, description, type)',
},
}
def build(
@ -47,24 +39,19 @@ class VectaraSelfQueryRetriverComponent(CustomComponent):
llm: BaseLanguageModel,
metadata_field_info: List[str],
) -> BaseRetriever:
metadata_field_obj = []
for meta in metadata_field_info:
meta_obj = json.loads(meta)
if 'name' not in meta_obj or 'description' not in meta_obj or 'type' not in meta_obj :
raise Exception('Incorrect metadata field info format.')
if "name" not in meta_obj or "description" not in meta_obj or "type" not in meta_obj:
raise Exception("Incorrect metadata field info format.")
attribute_info = AttributeInfo(
name = meta_obj['name'],
description = meta_obj['description'],
type = meta_obj['type'],
name=meta_obj["name"],
description=meta_obj["description"],
type=meta_obj["type"],
)
metadata_field_obj.append(attribute_info)
return SelfQueryRetriever.from_llm(
llm,
vectorstore,
document_content_description,
metadata_field_obj,
verbose=True
)
llm, vectorstore, document_content_description, metadata_field_obj, verbose=True
)

View file

@ -67,7 +67,9 @@ Human: {input}
class MidJourneyPromptChain(BaseCustomConversationChain):
"""MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."""
template: Optional[str] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program.
template: Optional[
str
] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program.
Your job is to provide detailed and creative descriptions that will inspire unique and interesting images from the AI.
Keep in mind that the AI is capable of understanding a wide range of language and can interpret abstract concepts, so feel free to be as imaginative and descriptive as possible.
For example, you could describe a scene from a futuristic city, or a surreal landscape filled with strange creatures.
@ -81,7 +83,9 @@ class MidJourneyPromptChain(BaseCustomConversationChain):
class TimeTravelGuideChain(BaseCustomConversationChain):
template: Optional[str] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information.
template: Optional[
str
] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information.
Current conversation:
{history}
Human: {input}

View file

@ -284,7 +284,7 @@
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
{ "name": "Connection", "value": "keep-alive" },
{ "name": "Cookie", "value": "access_tkn_lflw=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20; refresh_tkn_lflw=auto" },
{ "name": "Cookie", "value": "access_token_lf=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20; refresh_tkn_lflw=auto" },
{ "name": "Host", "value": "localhost:3000" },
{ "name": "Referer", "value": "http://localhost:3000/flows" },
{ "name": "Sec-Fetch-Dest", "value": "empty" },
@ -338,7 +338,7 @@
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
{ "name": "Connection", "value": "keep-alive" },
{ "name": "Cookie", "value": "access_tkn_lflw=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20; refresh_tkn_lflw=auto" },
{ "name": "Cookie", "value": "access_token_lf=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20; refresh_tkn_lflw=auto" },
{ "name": "Host", "value": "localhost:3000" },
{ "name": "Referer", "value": "http://localhost:3000/flows" },
{ "name": "Sec-Fetch-Dest", "value": "empty" },
@ -392,7 +392,7 @@
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
{ "name": "Connection", "value": "keep-alive" },
{ "name": "Cookie", "value": "access_tkn_lflw=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20; refresh_tkn_lflw=auto" },
{ "name": "Cookie", "value": "access_token_lf=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20; refresh_tkn_lflw=auto" },
{ "name": "Host", "value": "localhost:3000" },
{ "name": "Referer", "value": "http://localhost:3000/flows" },
{ "name": "Sec-Fetch-Dest", "value": "empty" },
@ -446,7 +446,7 @@
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
{ "name": "Connection", "value": "keep-alive" },
{ "name": "Cookie", "value": "access_tkn_lflw=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20; refresh_tkn_lflw=auto" },
{ "name": "Cookie", "value": "access_token_lf=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20; refresh_tkn_lflw=auto" },
{ "name": "Host", "value": "localhost:3000" },
{ "name": "Referer", "value": "http://localhost:3000/flow/2920dde2-5c24-4fe0-9c06-ef86b5a16a99" },
{ "name": "Sec-Fetch-Dest", "value": "empty" },
@ -500,7 +500,7 @@
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
{ "name": "Connection", "value": "keep-alive" },
{ "name": "Cookie", "value": "access_tkn_lflw=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20; refresh_tkn_lflw=auto" },
{ "name": "Cookie", "value": "access_token_lf=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20; refresh_tkn_lflw=auto" },
{ "name": "Host", "value": "localhost:3000" },
{ "name": "Referer", "value": "http://localhost:3000/flow/2920dde2-5c24-4fe0-9c06-ef86b5a16a99" },
{ "name": "Sec-Fetch-Dest", "value": "empty" },
@ -554,7 +554,7 @@
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
{ "name": "Connection", "value": "keep-alive" },
{ "name": "Cookie", "value": "access_tkn_lflw=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20; refresh_tkn_lflw=auto" },
{ "name": "Cookie", "value": "access_token_lf=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20; refresh_tkn_lflw=auto" },
{ "name": "Host", "value": "localhost:3000" },
{ "name": "Referer", "value": "http://localhost:3000/flow/2920dde2-5c24-4fe0-9c06-ef86b5a16a99" },
{ "name": "Sec-Fetch-Dest", "value": "empty" },

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
"@mui/material": "^5.14.7",
"@preact/signals-react": "^2.0.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
@ -66,7 +67,8 @@
"tailwindcss-animate": "^1.0.7",
"uuid": "^9.0.0",
"vite-plugin-svgr": "^3.2.0",
"web-vitals": "^2.1.4"
"web-vitals": "^2.1.4",
"zustand": "^4.4.7"
},
"scripts": {
"dev:docker": "vite --host 0.0.0.0",

View file

@ -1,18 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
</head>
<body>
<ul>
<li>
<a href="./e2e/index.html">e2e report</a>
</li>
<li>
<a href="./onlyFront/index.html">frontEnd Only report</a>
</li>
<li>
<a href="./e2e/index.html">e2e report</a>
</li>
<li>
<a href="./onlyFront/index.html">frontEnd Only report</a>
</li>
</ul>
</body>
</html>
</body>
</html>

View file

@ -2,15 +2,20 @@
@tailwind components;
@tailwind utilities;
/* Prevent react flow to overflow the page */
body {
overflow: hidden;
}
.App {
text-align: center;
}
.react-flow__node {
width: auto;
height: auto;
border-radius: auto;
min-width: inherit;
width: auto;
height: auto;
border-radius: auto;
min-width: inherit;
}
.App-logo {

View file

@ -1,6 +1,5 @@
import _ from "lodash";
import { useContext, useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import "reactflow/dist/style.css";
import "./App.css";
@ -15,37 +14,26 @@ import {
FETCH_ERROR_DESCRIPION,
FETCH_ERROR_MESSAGE,
} from "./constants/constants";
import { alertContext } from "./contexts/alertContext";
import { FlowsContext } from "./contexts/flowsContext";
import { locationContext } from "./contexts/locationContext";
import { typesContext } from "./contexts/typesContext";
import { AuthContext } from "./contexts/authContext";
import { getHealth } from "./controllers/API";
import Router from "./routes";
import useAlertStore from "./stores/alertStore";
import { useDarkStore } from "./stores/darkStore";
import useFlowsManagerStore from "./stores/flowsManagerStore";
import { useTypesStore } from "./stores/typesStore";
export default function App() {
let { setCurrent, setShowSideBar, setIsStackedOpen } =
useContext(locationContext);
let location = useLocation();
useEffect(() => {
setCurrent(location.pathname.replace(/\/$/g, "").split("/"));
setShowSideBar(true);
setIsStackedOpen(true);
}, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
const { hardReset } = useContext(FlowsContext);
const {
errorData,
errorOpen,
setErrorOpen,
noticeData,
noticeOpen,
setNoticeOpen,
successData,
successOpen,
setSuccessOpen,
loading,
} = useContext(alertContext);
const navigate = useNavigate();
const { fetchError } = useContext(typesContext);
const errorData = useAlertStore((state) => state.errorData);
const errorOpen = useAlertStore((state) => state.errorOpen);
const setErrorOpen = useAlertStore((state) => state.setErrorOpen);
const noticeData = useAlertStore((state) => state.noticeData);
const noticeOpen = useAlertStore((state) => state.noticeOpen);
const setNoticeOpen = useAlertStore((state) => state.setNoticeOpen);
const successData = useAlertStore((state) => state.successData);
const successOpen = useAlertStore((state) => state.successOpen);
const setSuccessOpen = useAlertStore((state) => state.setSuccessOpen);
const loading = useAlertStore((state) => state.loading);
const [fetchError, setFetchError] = useState(false);
// Initialize state variable for the list of alerts
const [alertsList, setAlertsList] = useState<
@ -131,28 +119,60 @@ export default function App() {
);
};
const { isAuthenticated } = useContext(AuthContext);
const refreshFlows = useFlowsManagerStore((state) => state.refreshFlows);
const getTypes = useTypesStore((state) => state.getTypes);
const refreshVersion = useDarkStore((state) => state.refreshVersion);
const refreshStars = useDarkStore((state) => state.refreshStars);
useEffect(() => {
refreshStars();
refreshVersion();
// If the user is authenticated, fetch the types. This code is important to check if the user is auth because of the execution order of the useEffect hooks.
if (isAuthenticated === true) {
// get data from db
getTypes().then(() => {
refreshFlows();
});
}
}, [isAuthenticated]);
useEffect(() => {
// Timer to call getHealth every 5 seconds
const timer = setInterval(() => {
getHealth()
.then(() => {
if (fetchError) setFetchError(false);
})
.catch(() => {
setFetchError(true);
});
}, 20000);
// Clean up the timer on component unmount
return () => {
clearInterval(timer);
};
}, []);
return (
//need parent component with width and height
<div className="flex h-full flex-col">
<ErrorBoundary
onReset={() => {
window.localStorage.removeItem("tabsData");
window.localStorage.clear();
hardReset();
window.location.href = window.location.href;
// any reset function
}}
FallbackComponent={CrashErrorComponent}
>
{loading ? (
{fetchError ? (
<FetchErrorComponent
description={FETCH_ERROR_DESCRIPION}
message={FETCH_ERROR_MESSAGE}
></FetchErrorComponent>
) : loading ? (
<div className="loading-page-panel">
{fetchError ? (
<FetchErrorComponent
description={FETCH_ERROR_DESCRIPION}
message={FETCH_ERROR_MESSAGE}
></FetchErrorComponent>
) : (
<LoadingComponent remSize={50} />
)}
<LoadingComponent remSize={50} />
</div>
) : (
<>

View file

@ -1,11 +1,5 @@
import { cloneDeep } from "lodash";
import React, {
ReactNode,
useContext,
useEffect,
useRef,
useState,
} from "react";
import React, { ReactNode, useEffect, useRef, useState } from "react";
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import CodeAreaComponent from "../../../../components/codeAreaComponent";
@ -26,16 +20,15 @@ import {
LANGFLOW_SUPPORTED_TYPES,
TOOLTIP_EMPTY,
} from "../../../../constants/constants";
import { alertContext } from "../../../../contexts/alertContext";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { typesContext } from "../../../../contexts/typesContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import { postCustomComponentUpdate } from "../../../../controllers/API";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useTypesStore } from "../../../../stores/typesStore";
import { APIClassType } from "../../../../types/api";
import { ParameterComponentType } from "../../../../types/components";
import { NodeDataType } from "../../../../types/flow";
import {
cleanEdges,
convertObjToArray,
convertValuesToNumbers,
hasDuplicateKeys,
@ -64,51 +57,32 @@ export default function ParameterComponent({
proxy,
showNode,
index = "",
isMinimized,
}: ParameterComponentType): JSX.Element {
const ref = useRef<HTMLDivElement>(null);
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
const { setErrorData, modalContextOpen } = useContext(alertContext);
const updateNodeInternals = useUpdateNodeInternals();
const [position, setPosition] = useState(0);
const { setTabsState, tabId, flows, tabsState, updateFlow } =
useContext(FlowsContext);
const setErrorData = useAlertStore((state) => state.setErrorData);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setNode = useFlowStore((state) => state.setNode);
const [dataRef, setDataRef] = useState(data);
const flow = flows.find((flow) => flow.id === tabId)?.data?.nodes ?? null;
// Update component position
useEffect(() => {
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
setPosition(ref.current.offsetTop + ref.current.clientHeight / 2);
updateNodeInternals(data.id);
}
}, [data.id, ref, ref.current, ref.current?.offsetTop, updateNodeInternals]);
useEffect(() => {
updateNodeInternals(data.id);
}, [data.id, position, updateNodeInternals]);
const flow = currentFlow?.data?.nodes ?? null;
const groupedEdge = useRef(null);
useEffect(() => {
setDataRef(data);
}, [modalContextOpen]);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const { reactFlowInstance, setFilterEdge } = useContext(typesContext);
let disabled =
reactFlowInstance
?.getEdges()
.some(
(edge) =>
edge.targetHandle ===
scapedJSONStringfy(proxy ? { ...id, proxy } : id)
) ?? false;
edges.some(
(edge) =>
edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id)
) ?? false;
const { data: myData } = useContext(typesContext);
const myData = useTypesStore((state) => state.data);
const { takeSnapshot } = useContext(undoRedoContext);
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const handleUpdateValues = async (name: string, data: NodeDataType) => {
const code = data.node?.template["code"]?.value;
@ -133,68 +107,50 @@ export default function ParameterComponent({
if (data.node!.template[name].value !== newValue) {
takeSnapshot();
}
data.node!.template[name].value = newValue;
updateNodeInternals(data.id);
setDataRef((old) => {
let newData = cloneDeep(old);
newData.node!.template[name].value = newValue;
return newData;
data.node!.template[name].value = newValue; // necessary to enable ctrl+z inside the input
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
newNode.data = {
...newNode.data,
};
newNode.data.node.template[name].value = newValue;
return newNode;
});
// Set state to pending
//@ts-ignore
if (data.node!.template[name].value !== newValue) {
const tabs = cloneDeep(tabsState);
tabs[tabId].isPending = false;
tabs[tabId].formKeysData = tabsState[tabId].formKeysData;
setTabsState({
...tabs,
});
}
renderTooltips();
};
const updateNodeInternals = useUpdateNodeInternals();
const handleNodeClass = (newNodeClass: APIClassType, code?: string): void => {
if (!data.node) return;
if (data.node!.template[name].value !== newNodeClass.template[name].value) {
if (data.node!.template[name].value !== code) {
takeSnapshot();
}
data.node! = {
...newNodeClass,
description: newNodeClass.description ?? data.node!.description,
display_name: newNodeClass.display_name ?? data.node!.display_name,
};
data.node!.template[name].value = code;
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
newNode.data = {
...newNode.data,
node: newNodeClass,
description: newNodeClass.description ?? data.node!.description,
display_name: newNodeClass.display_name ?? data.node!.display_name,
};
newNode.data.node.template[name].value = code;
return newNode;
});
updateNodeInternals(data.id);
// Set state to pending
//@ts-ignore
if (data.node!.template[name].value !== code) {
const tabs = cloneDeep(tabsState);
tabs[tabId].isPending = false;
tabs[tabId].formKeysData = tabsState[tabId].formKeysData;
setTabsState({
...tabs,
});
}
renderTooltips();
let flow = flows.find((flow) => flow.id === tabId);
setTimeout(() => {
//timeout necessary because ReactFlow updates are not async
if (reactFlowInstance && flow && flow.data) {
cleanEdges({
flow: {
edges: flow.data!.edges,
nodes: flow.data!.nodes,
},
updateEdge: (edge) => {
reactFlowInstance.setEdges(edge);
updateNodeInternals(data.id);
},
});
updateFlow(flow);
}
}, 50);
};
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
@ -317,21 +273,26 @@ export default function ParameterComponent({
<Handle
type={left ? "target" : "source"}
position={left ? Position.Left : Position.Right}
key={
proxy
? scapedJSONStringfy({ ...id, proxy })
: scapedJSONStringfy(id)
}
id={
proxy
? scapedJSONStringfy({ ...id, proxy })
: scapedJSONStringfy(id)
}
isValidConnection={(connection) =>
isValidConnection(connection, reactFlowInstance!)
isValidConnection(connection, nodes, edges)
}
className={classNames(
left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
"h-3 w-3 rounded-full border-2 bg-background"
"h-3 w-3 rounded-full border-2 bg-background",
isMinimized ? "mt-0" : ""
)}
style={{
borderColor: color,
top: position,
}}
onClick={() => {
setFilterEdge(groupedEdge.current);
@ -344,7 +305,7 @@ export default function ParameterComponent({
) : (
<div
ref={ref}
className="mt-1 flex w-full flex-wrap items-center justify-between bg-muted px-5 py-2"
className="relative mt-1 flex w-full flex-wrap items-center justify-between bg-muted px-5 py-2"
>
<>
<div
@ -390,13 +351,18 @@ export default function ParameterComponent({
<Handle
type={left ? "target" : "source"}
position={left ? Position.Left : Position.Right}
key={
proxy
? scapedJSONStringfy({ ...id, proxy })
: scapedJSONStringfy(id)
}
id={
proxy
? scapedJSONStringfy({ ...id, proxy })
: scapedJSONStringfy(id)
}
isValidConnection={(connection) =>
isValidConnection(connection, reactFlowInstance!)
isValidConnection(connection, nodes, edges)
}
className={classNames(
left ? "-ml-0.5 " : "-mr-0.5 ",
@ -404,7 +370,6 @@ export default function ParameterComponent({
)}
style={{
borderColor: color,
top: position,
}}
onClick={() => {
setFilterEdge(groupedEdge.current);
@ -435,8 +400,8 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"textarea-" + index}
data-testid={"textarea-" + index}
id={"textarea-" + data.node.template[name].name}
data-testid={"textarea-" + data.node.template[name].name}
/>
) : (
<InputComponent
@ -454,9 +419,7 @@ export default function ParameterComponent({
id={"toggle-" + index}
disabled={disabled}
enabled={data.node?.template[name].value ?? false}
setEnabled={(isEnabled) => {
handleOnNewValue(isEnabled);
}}
setEnabled={handleOnNewValue}
size="large"
/>
</div>
@ -558,10 +521,7 @@ export default function ParameterComponent({
}
: data.node!.template[name].value
}
onChange={(newValue) => {
data.node!.template[name].value = newValue;
handleOnNewValue(newValue);
}}
onChange={handleOnNewValue}
id="div-dict-input"
/>
</div>
@ -571,15 +531,14 @@ export default function ParameterComponent({
disabled={disabled}
editNode={false}
value={
dataRef.node!.template[name].value?.length === 0 ||
!dataRef.node!.template[name].value
data.node!.template[name].value?.length === 0 ||
!data.node!.template[name].value
? [{ "": "" }]
: convertObjToArray(dataRef.node!.template[name].value)
: convertObjToArray(data.node!.template[name].value)
}
duplicateKey={errorDuplicateKey}
onChange={(newValue) => {
const valueToNumbers = convertValuesToNumbers(newValue);
data.node!.template[name].value = valueToNumbers;
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
handleOnNewValue(valueToNumbers);
}}

View file

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../components/ShadTooltipComponent";
import Tooltip from "../../components/TooltipComponent";
@ -6,12 +6,10 @@ import IconComponent from "../../components/genericIconComponent";
import InputComponent from "../../components/inputComponent";
import { Textarea } from "../../components/ui/textarea";
import { priorityFields } from "../../constants/constants";
import { useSSE } from "../../contexts/SSEContext";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { typesContext } from "../../contexts/typesContext";
import { undoRedoContext } from "../../contexts/undoRedoContext";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useTypesStore } from "../../stores/typesStore";
import { validationStatusType } from "../../types/components";
import { NodeDataType } from "../../types/flow";
import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
@ -30,11 +28,9 @@ export default function GenericNode({
xPos: number;
yPos: number;
}): JSX.Element {
const { updateFlow, flows, tabId, saveCurrentFlow } =
useContext(FlowsContext);
const updateNodeInternals = useUpdateNodeInternals();
const { types, deleteNode, reactFlowInstance, setFilterEdge, getFilterEdge } =
useContext(typesContext);
const types = useTypesStore((state) => state.types);
const deleteNode = useFlowStore((state) => state.deleteNode);
const setNode = useFlowStore((state) => state.setNode);
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
const [inputName, setInputName] = useState(false);
const [nodeName, setNodeName] = useState(data.node!.display_name);
@ -45,10 +41,11 @@ export default function GenericNode({
const [validationStatus, setValidationStatus] =
useState<validationStatusType | null>(null);
const [handles, setHandles] = useState<boolean[] | []>([]);
const [isMinimized, setIsMinimized] = useState<boolean>(false);
let numberOfInputs: boolean[] = [];
const { modalContextOpen } = useContext(alertContext);
const updateNodeInternals = useUpdateNodeInternals();
const { takeSnapshot } = useContext(undoRedoContext);
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
function countHandles(): void {
numberOfInputs = Object.keys(data.node!.template)
@ -84,7 +81,8 @@ export default function GenericNode({
}, [data, data.node]);
// State for outline color
const { sseData, isBuilding } = useSSE();
const sseData = useFlowStore((state) => state.sseData);
const isBuilding = useFlowStore((state) => state.isBuilding);
useEffect(() => {
setNodeDescription(data.node!.description);
@ -109,6 +107,10 @@ export default function GenericNode({
const nameEditable = data.node?.flow || data.type === "CustomComponent";
useEffect(() => {
updateNodeInternals(data.id);
}, [isMinimized]);
return (
<>
<NodeToolbar>
@ -118,13 +120,16 @@ export default function GenericNode({
deleteNode={(id) => {
takeSnapshot();
deleteNode(id);
saveCurrentFlow();
}}
setShowNode={(show: boolean) => {
data.showNode = show;
setNode(data.id, (old) => ({
...old,
data: { ...old.data, showNode: show },
}));
}}
numberOfHandles={handles}
showNode={showNode}
setIsMinimized={setIsMinimized}
></NodeToolbarComponent>
</NodeToolbar>
@ -173,8 +178,16 @@ export default function GenericNode({
setInputName(false);
if (nodeName.trim() !== "") {
setNodeName(nodeName);
data.node!.display_name = nodeName;
updateNodeInternals(data.id);
setNode(data.id, (old) => ({
...old,
data: {
...old.data,
node: {
...old.data.node,
display_name: nodeName,
},
},
}));
} else {
setNodeName(data.node!.display_name);
}
@ -270,6 +283,7 @@ export default function GenericNode({
}
proxy={data.node?.template[templateField].proxy}
showNode={showNode}
isMinimized={isMinimized}
/>
)
)}
@ -296,6 +310,7 @@ export default function GenericNode({
type={data.node?.base_classes.join("|")}
left={false}
showNode={showNode}
isMinimized={isMinimized}
/>
</>
)}
@ -380,8 +395,16 @@ export default function GenericNode({
onBlur={() => {
setInputDescription(false);
setNodeDescription(nodeDescription);
data.node!.description = nodeDescription;
updateNodeInternals(data.id);
setNode(data.id, (old) => ({
...old,
data: {
...old.data,
node: {
...old.data.node,
description: nodeDescription,
},
},
}));
}}
value={nodeDescription}
onChange={(e) => setNodeDescription(e.target.value)}
@ -395,8 +418,16 @@ export default function GenericNode({
) {
setInputDescription(false);
setNodeDescription(nodeDescription);
data.node!.description = nodeDescription;
updateNodeInternals(data.id);
setNode(data.id, (old) => ({
...old,
data: {
...old.data,
node: {
...old.data.node,
description: nodeDescription,
},
},
}));
}
}}
/>
@ -484,6 +515,7 @@ export default function GenericNode({
}
proxy={data.node?.template[templateField].proxy}
showNode={showNode}
isMinimized={isMinimized}
/>
) : (
<></>
@ -527,6 +559,7 @@ export default function GenericNode({
type={data.node?.base_classes.join("|")}
left={false}
showNode={showNode}
isMinimized={isMinimized}
/>
)}
</>

View file

@ -1,23 +1,27 @@
import { useContext, useState } from "react";
import { useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "../../components/ui/popover";
import { alertContext } from "../../contexts/alertContext";
import useAlertStore from "../../stores/alertStore";
import { AlertDropdownType } from "../../types/alerts";
import SingleAlert from "./components/singleAlertComponent";
export default function AlertDropdown({
children,
}: AlertDropdownType): JSX.Element {
const {
notificationList,
clearNotificationList,
removeFromNotificationList,
setNotificationCenter,
} = useContext(alertContext);
const notificationList = useAlertStore((state) => state.notificationList);
const clearNotificationList = useAlertStore(
(state) => state.clearNotificationList
);
const removeFromNotificationList = useAlertStore(
(state) => state.removeFromNotificationList
);
const setNotificationCenter = useAlertStore(
(state) => state.setNotificationCenter
);
const [open, setOpen] = useState(false);

View file

@ -1,30 +1,16 @@
import { useContext, useEffect } from "react";
import { useContext } from "react";
import { Navigate } from "react-router-dom";
import { AuthContext } from "../../contexts/authContext";
export const ProtectedAdminRoute = ({ children }) => {
const {
isAdmin,
isAuthenticated,
logout,
getAuthentication,
userData,
autoLogin,
} = useContext(AuthContext);
useEffect(() => {
if (!isAuthenticated && !getAuthentication()) {
window.location.replace("/login");
logout();
}
}, [isAuthenticated, getAuthentication, logout, userData]);
const { isAdmin, isAuthenticated, logout, userData, autoLogin } =
useContext(AuthContext);
if (!isAuthenticated && !getAuthentication()) {
return <Navigate to="/login" replace />;
}
if ((userData && !isAdmin) || autoLogin) {
if (!isAuthenticated) {
logout();
} else if ((userData && !isAdmin) || autoLogin) {
return <Navigate to="/" replace />;
} else {
return children;
}
return children;
};

View file

@ -1,14 +1,11 @@
import { useContext } from "react";
import { Navigate } from "react-router-dom";
import { AuthContext } from "../../contexts/authContext";
export const ProtectedRoute = ({ children }) => {
const { isAuthenticated, logout, getAuthentication } =
useContext(AuthContext);
if (!isAuthenticated && !getAuthentication()) {
const { isAuthenticated, logout } = useContext(AuthContext);
if (!isAuthenticated) {
logout();
return <Navigate to="/login" replace />;
} else {
return children;
}
return children;
};

View file

@ -3,14 +3,14 @@ import { Navigate } from "react-router-dom";
import { AuthContext } from "../../contexts/authContext";
export const ProtectedLoginRoute = ({ children }) => {
const { getAuthentication, autoLogin } = useContext(AuthContext);
const { isAuthenticated, autoLogin } = useContext(AuthContext);
if (autoLogin === true) {
window.location.replace("/");
return <Navigate to="/" replace />;
}
if (getAuthentication()) {
if (isAuthenticated) {
window.location.replace("/");
return <Navigate to="/" replace />;
}

View file

@ -1,9 +1,9 @@
import { useContext, useEffect, useState } from "react";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { StoreContext } from "../../contexts/storeContext";
import { useEffect, useState } from "react";
import { getComponent, postLikeComponent } from "../../controllers/API";
import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore";
import { storeComponent } from "../../types/store";
import cloneFLowWithParent from "../../utils/storeUtils";
import { cn } from "../../utils/utils";
@ -32,9 +32,10 @@ export default function CollectionCardComponent({
button?: JSX.Element;
onDelete?: () => void;
}) {
const { addFlow } = useContext(FlowsContext);
const { setSuccessData, setErrorData } = useContext(alertContext);
const { setValidApiKey } = useContext(StoreContext);
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
const isStore = false;
const [loading, setLoading] = useState(false);
const [loadingLike, setLoadingLike] = useState(false);

View file

@ -1,15 +1,12 @@
import { Transition } from "@headlessui/react";
import { useContext, useState } from "react";
import { useState } from "react";
import Loading from "../../../components/ui/loading";
import { useSSE } from "../../../contexts/SSEContext";
import { alertContext } from "../../../contexts/alertContext";
import { typesContext } from "../../../contexts/typesContext";
import { postBuildInit } from "../../../controllers/API";
import { FlowType } from "../../../types/flow";
import { FlowsContext } from "../../../contexts/flowsContext";
import useAlertStore from "../../../stores/alertStore";
import useFlowStore from "../../../stores/flowStore";
import { parsedDataType } from "../../../types/components";
import { FlowsState } from "../../../types/tabs";
import { validateNodes } from "../../../utils/reactflowUtils";
import RadialProgressComponent from "../../RadialProgress";
import IconComponent from "../../genericIconComponent";
@ -24,11 +21,15 @@ export default function BuildTrigger({
setIsBuilt: any;
isBuilt: boolean;
}): JSX.Element {
const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE();
const { reactFlowInstance } = useContext(typesContext);
const { setTabsState, saveFlow } = useContext(FlowsContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
const [isIconTouched, setIsIconTouched] = useState(false);
const updateSSEData = useFlowStore((state) => state.updateSSEData);
const isBuilding = useFlowStore((state) => state.isBuilding);
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setFlowState = useFlowStore((state) => state.setFlowState);
const eventClick = isBuilding ? "pointer-events-none" : "";
const [progress, setProgress] = useState(0);
@ -37,10 +38,7 @@ export default function BuildTrigger({
if (isBuilding) {
return;
}
const errors = validateNodes(
reactFlowInstance!.getNodes(),
reactFlowInstance!.getEdges()
);
const errors = validateNodes(nodes, edges);
if (errors.length > 0) {
setErrorData({
title: "Oops! Looks like you missed something",
@ -76,7 +74,6 @@ export default function BuildTrigger({
}
async function streamNodeData(flow: FlowType) {
// Step 1: Make a POST request to send the flow data and receive a unique session ID
const id = saveFlow(flow, true);
const response = await postBuildInit(flow);
const { flowId } = response.data;
// Step 2: Use the session ID to establish an SSE connection using EventSource
@ -99,16 +96,7 @@ export default function BuildTrigger({
// If the event is a log, log it
setSuccessData({ title: parsedData.log });
} else if (parsedData.input_keys !== undefined) {
//@ts-ignore
setTabsState((old: FlowsState) => {
return {
...old,
[flowId]: {
...old[flowId],
formKeysData: parsedData,
},
};
});
setFlowState(parsedData);
} else {
// Otherwise, process the data
const isValid = processStreamResult(parsedData);
@ -154,14 +142,6 @@ export default function BuildTrigger({
}
}
const handleMouseEnter = () => {
setIsIconTouched(true);
};
const handleMouseLeave = () => {
setIsIconTouched(false);
};
return (
<Transition
show={!open}
@ -179,8 +159,6 @@ export default function BuildTrigger({
onClick={() => {
handleBuild(flow);
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<button>
<div className="round-button-div">

View file

@ -1,13 +1,12 @@
import { Transition } from "@headlessui/react";
import { useContext } from "react";
import {
CHAT_CANNOT_OPEN_DESCRIPTION,
CHAT_CANNOT_OPEN_TITLE,
FLOW_NOT_BUILT_DESCRIPTION,
FLOW_NOT_BUILT_TITLE,
} from "../../../constants/constants";
import { alertContext } from "../../../contexts/alertContext";
import useAlertStore from "../../../stores/alertStore";
import { chatTriggerPropType } from "../../../types/components";
import IconComponent from "../../genericIconComponent";
@ -17,7 +16,7 @@ export default function ChatTrigger({
isBuilt,
canOpen,
}: chatTriggerPropType): JSX.Element {
const { setErrorData } = useContext(alertContext);
const setErrorData = useAlertStore((state) => state.setErrorData);
function handleClick(): void {
if (isBuilt) {

View file

@ -1,20 +1,19 @@
import { useContext, useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useNodes } from "reactflow";
import { ChatType } from "../../types/chat";
import BuildTrigger from "./buildTrigger";
import ChatTrigger from "./chatTrigger";
import * as _ from "lodash";
import { FlowsContext } from "../../contexts/flowsContext";
import { getBuildStatus } from "../../controllers/API";
import FormModal from "../../modals/formModal";
import useFlowStore from "../../stores/flowStore";
import { NodeType } from "../../types/flow";
export default function Chat({ flow }: ChatType): JSX.Element {
const [open, setOpen] = useState(false);
const [canOpen, setCanOpen] = useState(false);
const { tabsState, isBuilt, setIsBuilt } = useContext(FlowsContext);
const isBuilt = useFlowStore((state) => state.isBuilt);
const setIsBuilt = useFlowStore((state) => state.setIsBuilt);
const flowState = useFlowStore((state) => state.flowState);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (
@ -32,17 +31,6 @@ export default function Chat({ flow }: ChatType): JSX.Element {
};
}, [isBuilt]);
useEffect(() => {
// Define an async function within the useEffect hook
const fetchBuildStatus = async () => {
const response = await getBuildStatus(flow.id);
setIsBuilt(response.data.built);
};
// Call the async function
fetchBuildStatus();
}, [flow]);
const prevNodesRef = useRef<any[] | undefined>();
const nodes: NodeType[] = useNodes();
useEffect(() => {
@ -50,27 +38,11 @@ export default function Chat({ flow }: ChatType): JSX.Element {
const currentNodes = nodes.map((node: NodeType) =>
_.cloneDeep(node.data.node?.template)
);
if (
tabsState &&
tabsState[flow.id] &&
tabsState[flow.id].isPending &&
JSON.stringify(prevNodes) !== JSON.stringify(currentNodes)
) {
if (JSON.stringify(prevNodes) !== JSON.stringify(currentNodes)) {
setIsBuilt(false);
}
if (
tabsState &&
tabsState[flow.id] &&
tabsState[flow.id].formKeysData &&
tabsState[flow.id].formKeysData.input_keys !== null
) {
setCanOpen(true);
} else {
setCanOpen(false);
}
prevNodesRef.current = currentNodes;
}, [tabsState, flow.id]);
}, [flowState, flow.id]);
return (
<>
@ -81,19 +53,11 @@ export default function Chat({ flow }: ChatType): JSX.Element {
setIsBuilt={setIsBuilt}
isBuilt={isBuilt}
/>
{isBuilt &&
tabsState[flow.id] &&
tabsState[flow.id].formKeysData &&
canOpen && (
<FormModal
key={flow.id}
flow={flow}
open={open}
setOpen={setOpen}
/>
)}
{isBuilt && flowState && !!flowState?.input_keys && (
<FormModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
)}
<ChatTrigger
canOpen={canOpen}
canOpen={!!flowState?.input_keys}
open={open}
setOpen={setOpen}
isBuilt={isBuilt}

View file

@ -19,11 +19,11 @@ export default function CodeAreaComponent({
typeof value == "string" ? value : JSON.stringify(value)
);
useEffect(() => {
if (disabled) {
if (disabled && myValue !== "") {
setMyValue("");
onChange("");
}
}, [disabled, onChange]);
}, [disabled]);
useEffect(() => {
setMyValue(typeof value == "string" ? value : JSON.stringify(value));

View file

@ -1,5 +1,5 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import AccordionComponent from "../../components/AccordionComponent";
@ -28,14 +28,13 @@ import {
TabsTrigger,
} from "../../components/ui/tabs";
import { LANGFLOW_SUPPORTED_TYPES } from "../../constants/constants";
import { darkContext } from "../../contexts/darkContext";
import { typesContext } from "../../contexts/typesContext";
import { useDarkStore } from "../../stores/darkStore";
import useFlowStore from "../../stores/flowStore";
import { codeTabsPropsType } from "../../types/components";
import {
convertObjToArray,
convertValuesToNumbers,
hasDuplicateKeys,
unselectAllNodes,
} from "../../utils/reactflowUtils";
import { classNames } from "../../utils/utils";
import DictComponent from "../dictComponent";
@ -53,8 +52,11 @@ export default function CodeTabsComponent({
const [isCopied, setIsCopied] = useState<Boolean>(false);
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
const { dark } = useContext(darkContext);
const { reactFlowInstance } = useContext(typesContext);
const dark = useDarkStore((state) => state.dark);
const unselectAll = useFlowStore((state) => state.unselectAll);
const setNodes = useFlowStore((state) => state.setNodes);
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
useEffect(() => {
@ -65,12 +67,7 @@ export default function CodeTabsComponent({
useEffect(() => {
if (tweaks && data) {
unselectAllNodes({
data,
updateNodes: (nodes) => {
reactFlowInstance?.setNodes(nodes);
},
});
unselectAll();
}
}, []);
@ -591,14 +588,7 @@ export default function CodeTabsComponent({
].type === "prompt" ? (
<div className="mx-auto">
<PromptAreaComponent
readonly={
node.data.node?.flow &&
node.data.node.template[
templateField
].dynamic
? true
: false
}
readonly={true}
editNode={true}
disabled={false}
value={
@ -641,14 +631,7 @@ export default function CodeTabsComponent({
<CodeAreaComponent
disabled={false}
editNode={true}
readonly={
node.data.node?.flow &&
node.data.node.template[
templateField
].dynamic
? true
: false
}
readonly={true}
value={
!node.data.node.template[
templateField

View file

@ -6,7 +6,7 @@ import { classNames } from "../../utils/utils";
import { Input } from "../ui/input";
export default function DictComponent({
value,
value = [],
onChange,
disabled,
editNode = false,

View file

@ -15,10 +15,10 @@ export default function FloatComponent({
const max = rangeSpec?.max ?? 2;
// Clear component state
useEffect(() => {
if (disabled) {
if (disabled && value !== "") {
onChange("");
}
}, [disabled, onChange]);
}, [disabled]);
return (
<div className="w-full">
@ -48,7 +48,7 @@ export default function FloatComponent({
onChange(event.target.value);
}}
onKeyDown={(e) => {
handleKeyDown(e, value, "0");
handleKeyDown(e, value, "");
}}
/>
</div>

View file

@ -1,5 +1,4 @@
import { useContext, useState } from "react";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { useState } from "react";
import {
DropdownMenu,
DropdownMenuContent,
@ -9,17 +8,18 @@ import {
} from "../../../ui/dropdown-menu";
import { useNavigate } from "react-router-dom";
import { alertContext } from "../../../../contexts/alertContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
import { menuBarPropsType } from "../../../../types/components";
import useAlertStore from "../../../../stores/alertStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import IconComponent from "../../../genericIconComponent";
import { Button } from "../../../ui/button";
export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
const { addFlow } = useContext(FlowsContext);
const { setErrorData } = useContext(alertContext);
const { undo, redo } = useContext(undoRedoContext);
export const MenuBar = (): JSX.Element => {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const setErrorData = useAlertStore((state) => state.setErrorData);
const undo = useFlowsManagerStore((state) => state.undo);
const redo = useFlowsManagerStore((state) => state.redo);
const [openSettings, setOpenSettings] = useState(false);
const navigate = useNavigate();
@ -34,9 +34,8 @@ export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
setErrorData(err as { title: string; list?: Array<string> });
}
}
let current_flow = flows.find((flow) => flow.id === tabId);
return (
return currentFlow ? (
<div className="round-button-div">
<button
onClick={() => {
@ -50,9 +49,7 @@ export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
<DropdownMenuTrigger asChild>
<Button asChild variant="primary" size="sm">
<div className="header-menu-bar-display">
<div className="header-menu-flow-name">
{current_flow!.name}
</div>
<div className="header-menu-flow-name">{currentFlow.name}</div>
<IconComponent name="ChevronDown" className="h-4 w-4" />
</div>
</Button>
@ -108,6 +105,8 @@ export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
></FlowSettingsModal>
</div>
</div>
) : (
<></>
);
};

View file

@ -1,14 +1,13 @@
import { useContext } from "react";
import { useContext, useEffect } from "react";
import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
import { Link, useLocation, useNavigate } from "react-router-dom";
import AlertDropdown from "../../alerts/alertDropDown";
import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { darkContext } from "../../contexts/darkContext";
import { StoreContext } from "../../contexts/storeContext";
import { FlowsContext } from "../../contexts/flowsContext";
import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore";
import { useStoreStore } from "../../stores/storeStore";
import { gradients } from "../../utils/styleUtils";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
@ -24,25 +23,33 @@ import { Separator } from "../ui/separator";
import MenuBar from "./components/menuBar";
export default function Header(): JSX.Element {
const { flows, tabId } = useContext(FlowsContext);
const { dark, setDark } = useContext(darkContext);
const { notificationCenter } = useContext(alertContext);
const notificationCenter = useAlertStore((state) => state.notificationCenter);
const location = useLocation();
const { logout, autoLogin, isAdmin, userData } = useContext(AuthContext);
const { hasStore } = useContext(StoreContext);
const { stars, gradientIndex } = useContext(darkContext);
const navigate = useNavigate();
const hasStore = useStoreStore((state) => state.hasStore);
const dark = useDarkStore((state) => state.dark);
const setDark = useDarkStore((state) => state.setDark);
const stars = useDarkStore((state) => state.stars);
useEffect(() => {
if (dark) {
document.getElementById("body")!.classList.add("dark");
} else {
document.getElementById("body")!.classList.remove("dark");
}
window.localStorage.setItem("isDark", dark.toString());
}, [dark]);
return (
<div className="header-arrangement">
<div className="header-start-display lg:w-[30%]">
<Link to="/">
<span className="ml-4 text-2xl"></span>
</Link>
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
<MenuBar flows={flows} tabId={tabId} />
)}
<MenuBar />
</div>
<div className="round-button-div">
<Link to="/">
@ -95,7 +102,7 @@ export default function Header(): JSX.Element {
>
<FaGithub className="h-5 w-5" />
<div className="hidden lg:block">Star</div>
<div className="header-github-display">{stars}</div>
<div className="header-github-display">{stars ?? 0}</div>
</a>
<a
href="https://twitter.com/logspace_ai"
@ -159,7 +166,10 @@ export default function Header(): JSX.Element {
<button
className={
"h-7 w-7 rounded-full focus-visible:outline-0 " +
(userData?.profile_image ?? gradients[gradientIndex])
(userData?.profile_image ??
gradients[
parseInt(userData?.id ?? "", 30) % gradients.length
])
}
/>
</DropdownMenuTrigger>
@ -184,7 +194,6 @@ export default function Header(): JSX.Element {
className="cursor-pointer"
onClick={() => {
logout();
navigate("/login");
}}
>
Sign Out

View file

@ -24,10 +24,10 @@ export default function InputComponent({
const refInput = useRef<HTMLInputElement>(null);
// Clear component state
useEffect(() => {
if (disabled) {
if (disabled && value !== "") {
onChange("");
}
}, [disabled, onChange]);
}, [disabled]);
return (
<div className="relative w-full">
@ -59,9 +59,6 @@ export default function InputComponent({
e.preventDefault();
}}
onKeyDown={(e) => {
if (e.ctrlKey && e.key === "c") {
// Perform any actions you need when Ctrl+C is detected
}
handleKeyDown(e, value, "");
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
}}

View file

@ -1,7 +1,7 @@
import { useContext, useEffect, useState } from "react";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { useEffect, useState } from "react";
import { uploadFile } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { FileComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
@ -13,14 +13,14 @@ export default function InputFileComponent({
onFileChange,
editNode = false,
}: FileComponentType): JSX.Element {
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const [myValue, setMyValue] = useState(value);
const [loading, setLoading] = useState(false);
const { setErrorData } = useContext(alertContext);
const { tabId } = useContext(FlowsContext);
const setErrorData = useAlertStore((state) => state.setErrorData);
// Clear component state
useEffect(() => {
if (disabled) {
if (disabled && value !== "") {
setMyValue("");
onChange("");
onFileChange("");
@ -58,7 +58,7 @@ export default function InputFileComponent({
// Check if the file type is correct
if (file && checkFileType(file.name)) {
// Upload the file
uploadFile(file, tabId)
uploadFile(file, currentFlowId)
.then((res) => res.data)
.then((data) => {
console.log("File uploaded successfully");

View file

@ -13,7 +13,7 @@ export default function InputListComponent({
editNode = false,
}: InputListComponentType): JSX.Element {
useEffect(() => {
if (disabled) {
if (disabled && value.length > 0 && value[0] !== "") {
onChange([""]);
}
}, [disabled]);
@ -23,6 +23,8 @@ export default function InputListComponent({
value = [value];
}
if (!value.length) value = [""];
return (
<div
className={classNames(
@ -44,12 +46,6 @@ export default function InputListComponent({
newInputList[idx] = event.target.value;
onChange(newInputList);
}}
onKeyDown={(e) => {
if (e.ctrlKey && e.key === "Backspace") {
e.preventDefault();
e.stopPropagation();
}
}}
/>
{idx === value.length - 1 ? (
<button

View file

@ -1,6 +1,9 @@
import { useEffect } from "react";
import { IntComponentType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils";
import {
handleKeyDown,
handleOnlyIntegerInput,
} from "../../utils/reactflowUtils";
import { Input } from "../ui/input";
export default function IntComponent({
@ -14,7 +17,7 @@ export default function IntComponent({
// Clear component state
useEffect(() => {
if (disabled) {
if (disabled && value !== "") {
onChange("");
}
}, [disabled, onChange]);
@ -24,29 +27,12 @@ export default function IntComponent({
<Input
id={id}
onKeyDown={(event) => {
if (
event.key !== "Backspace" &&
event.key !== "Enter" &&
event.key !== "Delete" &&
event.key !== "ArrowLeft" &&
event.key !== "ArrowRight" &&
event.key !== "Control" &&
event.key !== "Meta" &&
event.key !== "Shift" &&
event.key !== "c" &&
event.key !== "v" &&
event.key !== "a" &&
event.key !== "ArrowUp" &&
event.key !== "ArrowDown" &&
!/^[-]?\d*$/.test(event.key)
) {
event.preventDefault();
}
handleKeyDown(event, value, "0");
handleOnlyIntegerInput(event);
handleKeyDown(event, value, "");
}}
type="number"
step="1"
min={min}
min={0}
onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
if (Number(event.target.value) < min) {
event.target.value = min.toString();

View file

@ -14,7 +14,7 @@ export default function KeypairListComponent({
duplicateKey,
}: KeyPairListComponentType): JSX.Element {
useEffect(() => {
if (disabled) {
if (disabled && value.length > 0 && value[0] !== "") {
onChange([""]);
}
}, [disabled]);
@ -65,12 +65,6 @@ export default function KeypairListComponent({
)}
placeholder="Type key..."
onChange={(event) => handleChangeKey(event, index)}
onKeyDown={(e) => {
if (e.ctrlKey && e.key === "Backspace") {
e.preventDefault();
e.stopPropagation();
}
}}
/>
<Input

View file

@ -12,7 +12,7 @@ export default function PageLayout({
description: string;
children: React.ReactNode;
button?: React.ReactNode;
betaIcon: boolean;
betaIcon?: boolean;
}) {
return (
<div className="flex h-screen w-full flex-col">

View file

@ -17,7 +17,7 @@ export default function PromptAreaComponent({
readonly = false,
}: PromptAreaComponentType): JSX.Element {
useEffect(() => {
if (disabled) {
if (disabled && value !== "") {
onChange("");
}
}, [disabled]);

View file

@ -1,9 +1,9 @@
import { useContext } from "react";
import { Navigate } from "react-router-dom";
import { StoreContext } from "../../contexts/storeContext";
import { useStoreStore } from "../../stores/storeStore";
export const StoreGuard = ({ children }) => {
const { hasStore } = useContext(StoreContext);
const hasStore = useStoreStore((state) => state.hasStore);
if (!hasStore) {
return <Navigate to="/flows" replace />;
}

View file

@ -1,5 +1,4 @@
import { useContext, useEffect, useRef, useState } from "react";
import { darkContext } from "../../contexts/darkContext";
import { useEffect, useRef, useState } from "react";
import { cn } from "../../utils/utils";
import { Badge } from "../ui/badge";
@ -24,7 +23,6 @@ export function TagsSelector({
: selectedTags.filter((_, i) => i !== index);
setSelectedTags(newArray);
};
const { dark } = useContext(darkContext);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const fadeContainerRef = useRef<HTMLDivElement>(null);

View file

@ -14,7 +14,7 @@ export default function TextAreaComponent({
}: TextAreaComponentType): JSX.Element {
// Clear text area
useEffect(() => {
if (disabled) {
if (disabled && value !== "") {
onChange("");
}
}, [disabled]);

View file

@ -24,16 +24,16 @@ const AccordionTrigger = React.forwardRef<
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDownIcon className="h-4 w-4 text-muted-foreground transition-transform duration-200" />
<AccordionPrimitive.Trigger asChild ref={ref} {...props}>
<div
className={cn(
"flex flex-1 cursor-pointer items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180",
className
)}
>
{children}
<ChevronDownIcon className="h-4 w-4 text-muted-foreground transition-transform duration-200" />
</div>
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));

View file

@ -26,4 +26,26 @@ const Checkbox = React.forwardRef<
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox };
const CheckBoxDiv = ({
className = "",
checked,
}: {
className?: string;
checked?: boolean;
}) => (
<div
className={cn(
className,
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
checked ? "bg-primary text-primary-foreground" : ""
)}
>
{checked && (
<div className="flex items-center justify-center text-current">
<IconComponent name="Check" className="h-4 w-4" />
</div>
)}
</div>
);
export { CheckBoxDiv, Checkbox };

View file

@ -1,34 +0,0 @@
import { createContext, useCallback, useContext, useState } from "react";
const initialValue = {
updateSSEData: ({}) => {},
sseData: {},
isBuilding: false,
setIsBuilding: (isBuilding: boolean) => {},
};
const SSEContext = createContext(initialValue);
export function useSSE() {
return useContext(SSEContext);
}
export function SSEProvider({ children }) {
const [sseData, setSSEData] = useState({});
const [isBuilding, setIsBuilding] = useState(false);
const updateSSEData = useCallback((newData: any) => {
setSSEData((prevData) => ({
...prevData,
...newData,
}));
}, []);
return (
<SSEContext.Provider
value={{ sseData, updateSSEData, isBuilding, setIsBuilding }}
>
{children}
</SSEContext.Provider>
);
}

View file

@ -1,154 +0,0 @@
import { createContext, ReactNode, useState } from "react";
import { AlertItemType } from "../types/alerts";
import { alertContextType } from "../types/typesContext";
import _ from "lodash";
//initial values to alertContextType
const initialValue: alertContextType = {
errorData: { title: "", list: [] },
setErrorData: () => {},
loading: true,
setLoading: () => {},
errorOpen: false,
setErrorOpen: () => {},
noticeData: { title: "", link: "" },
setNoticeData: () => {},
noticeOpen: false,
setNoticeOpen: () => {},
successData: { title: "" },
setSuccessData: () => {},
successOpen: false,
setSuccessOpen: () => {},
notificationCenter: false,
setNotificationCenter: () => {},
notificationList: [],
pushNotificationList: () => {},
clearNotificationList: () => {},
removeFromNotificationList: () => {},
modalContextOpen: false,
setModalContextOpen: (open: boolean) => {},
};
export const alertContext = createContext<alertContextType>(initialValue);
export function AlertProvider({ children }: { children: ReactNode }) {
const [errorData, setErrorDataState] = useState<{
title: string;
list?: Array<string>;
}>({ title: "", list: [] });
const [errorOpen, setErrorOpen] = useState(false);
const [loading, setLoading] = useState(true);
const [noticeData, setNoticeDataState] = useState<{
title: string;
link?: string;
}>({ title: "", link: "" });
const [noticeOpen, setNoticeOpen] = useState(false);
const [successData, setSuccessDataState] = useState<{ title: string }>({
title: "",
});
const [successOpen, setSuccessOpen] = useState(false);
const [notificationCenter, setNotificationCenter] = useState(false);
const [notificationList, setNotificationList] = useState<AlertItemType[]>([]);
const [isTweakPage, setIsTweakPage] = useState<boolean>(false);
const [modalContextOpen, setModalContextOpen] = useState<boolean>(false);
const pushNotificationList = (notification: AlertItemType) => {
setNotificationList((old) => {
let newNotificationList = _.cloneDeep(old);
newNotificationList.unshift(notification);
return newNotificationList;
});
};
/**
* 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
*/
function setErrorData(newState: { title: string; list?: Array<string> }) {
if (newState.title && newState.title !== "") {
setErrorDataState(newState);
setErrorOpen(true);
setNotificationCenter(true);
pushNotificationList({
type: "error",
title: newState.title,
list: newState.list,
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.
* @param newState An object containing the title of the notice and optionally a link.
*/
function setNoticeData(newState: { title: string; link?: string }) {
if (newState.title && newState.title !== "") {
setNoticeDataState(newState);
setNoticeOpen(true);
// Add new notice to notification center
setNotificationCenter(true);
pushNotificationList({
type: "notice",
title: newState.title,
link: newState.link,
id: _.uniqueId(),
});
}
}
/**
* 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.
*/
function setSuccessData(newState: { title: string }) {
// If the new state has a "title" property, add a new success notification to the list
if (newState.title && newState.title !== "") {
setSuccessDataState(newState); // update the success data state with the provided new state
setSuccessOpen(true); // open the success alert
setNotificationCenter(true); // show the notification center
pushNotificationList({
// add the new notification to the list
type: "success",
title: newState.title,
id: _.uniqueId(),
});
}
}
function clearNotificationList() {
setNotificationList([]);
}
function removeFromNotificationList(index: string) {
// set the notification list to a new array that filters out the alert with the matching id
setNotificationList((prevAlertsList) =>
prevAlertsList.filter((alert) => alert.id !== index)
);
}
return (
<alertContext.Provider
value={{
removeFromNotificationList,
clearNotificationList,
notificationList,
loading,
setLoading,
pushNotificationList,
setNotificationCenter,
notificationCenter,
errorData,
setErrorData,
errorOpen,
setErrorOpen,
noticeData,
setNoticeData,
noticeOpen,
setNoticeOpen,
successData,
setSuccessData,
successOpen,
setSuccessOpen,
modalContextOpen,
setModalContextOpen,
}}
>
{children}
</alertContext.Provider>
);
}

View file

@ -1,21 +1,24 @@
import { createContext, useContext, useEffect, useState } from "react";
import { createContext, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import Cookies from "universal-cookie";
import { autoLogin as autoLoginApi, getLoggedUser } from "../controllers/API";
import {
autoLogin as autoLoginApi,
getLoggedUser,
requestLogout,
} from "../controllers/API";
import useAlertStore from "../stores/alertStore";
import { Users } from "../types/api";
import { AuthContextType } from "../types/contexts/auth";
import { alertContext } from "./alertContext";
const initialValue: AuthContextType = {
isAdmin: false,
setIsAdmin: () => false,
isAuthenticated: false,
accessToken: null,
refreshToken: null,
login: () => {},
logout: () => {},
logout: () => new Promise(() => {}),
userData: null,
setUserData: () => {},
getAuthentication: () => false,
authenticationErrorCount: 0,
autoLogin: false,
setAutoLogin: () => {},
@ -27,24 +30,24 @@ const initialValue: AuthContextType = {
export const AuthContext = createContext<AuthContextType>(initialValue);
export function AuthProvider({ children }): React.ReactElement {
const navigate = useNavigate();
const cookies = new Cookies();
const [accessToken, setAccessToken] = useState<string | null>(
cookies.get("access_tkn_lflw")
cookies.get("access_token_lf") ?? null
);
const [refreshToken, setRefreshToken] = useState<string | null>(
cookies.get("refresh_tkn_lflw")
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(
!!cookies.get("access_token_lf")
);
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
const [isAdmin, setIsAdmin] = useState<boolean>(false);
const [userData, setUserData] = useState<Users | null>(null);
const [autoLogin, setAutoLogin] = useState<boolean>(false);
const { setLoading } = useContext(alertContext);
const setLoading = useAlertStore((state) => state.setLoading);
const [apiKey, setApiKey] = useState<string | null>(
cookies.get("apikey_tkn_lflw")
);
useEffect(() => {
const storedAccessToken = cookies.get("access_tkn_lflw");
const storedAccessToken = cookies.get("access_token_lf");
if (storedAccessToken) {
setAccessToken(storedAccessToken);
}
@ -64,7 +67,7 @@ export function AuthProvider({ children }): React.ReactElement {
.then((user) => {
if (user && user["access_token"]) {
user["refresh_token"] = "auto";
login(user["access_token"], user["refresh_token"]);
login(user["access_token"]);
setUserData(user);
setAutoLogin(true);
setLoading(false);
@ -72,48 +75,50 @@ export function AuthProvider({ children }): React.ReactElement {
})
.catch((error) => {
setAutoLogin(false);
if (getAuthentication() && !isLoginPage) {
getLoggedUser()
.then((user) => {
setUserData(user);
setLoading(false);
const isSuperUser = user!.is_superuser;
setIsAdmin(isSuperUser);
})
.catch((error) => {
console.log("auth context");
setLoading(false);
});
if (isAuthenticated && !isLoginPage) {
getUser();
} else {
setLoading(false);
}
});
}, [setUserData, setLoading, autoLogin, setIsAdmin]);
function getAuthentication() {
const storedRefreshToken = cookies.get("refresh_tkn_lflw");
const storedAccess = cookies.get("access_tkn_lflw");
const auth = storedAccess && storedRefreshToken ? true : false;
return auth;
function getUser() {
getLoggedUser()
.then((user) => {
setUserData(user);
setLoading(false);
const isSuperUser = user!.is_superuser;
setIsAdmin(isSuperUser);
})
.catch((error) => {
console.log("auth context");
setLoading(false);
});
}
function login(newAccessToken: string, refreshToken: string) {
cookies.set("access_tkn_lflw", newAccessToken, { path: "/" });
cookies.set("refresh_tkn_lflw", refreshToken, { path: "/" });
function login(newAccessToken: string) {
setAccessToken(newAccessToken);
setRefreshToken(refreshToken);
setIsAuthenticated(true);
getUser();
}
function logout() {
cookies.remove("access_tkn_lflw", { path: "/" });
cookies.remove("refresh_tkn_lflw", { path: "/" });
cookies.remove("apikey_tkn_lflw", { path: "/" });
setIsAdmin(false);
setUserData(null);
setAccessToken(null);
setRefreshToken(null);
setIsAuthenticated(false);
async function logout() {
if (autoLogin) {
return;
}
try {
await requestLogout();
cookies.remove("apikey_tkn_lflw", { path: "/" });
setIsAdmin(false);
setUserData(null);
setAccessToken(null);
setIsAuthenticated(false);
navigate("/login");
} catch (error) {
console.error(error);
throw error;
}
}
function storeApiKey(apikey: string) {
@ -127,14 +132,12 @@ export function AuthProvider({ children }): React.ReactElement {
value={{
isAdmin,
setIsAdmin,
isAuthenticated: !!accessToken,
isAuthenticated,
accessToken,
refreshToken,
login,
logout,
setUserData,
userData,
getAuthentication,
authenticationErrorCount: 0,
setAutoLogin,
autoLogin,

View file

@ -1,57 +0,0 @@
import { createContext, useEffect, useState } from "react";
import { getRepoStars } from "../controllers/API";
import { darkContextType } from "../types/typesContext";
const initialValue = {
dark: {},
setDark: () => {},
stars: 0,
setStars: (stars) => 0,
gradientIndex: 0,
setGradientIndex: () => 0,
};
export const darkContext = createContext<darkContextType>(initialValue);
export function DarkProvider({ children }) {
const [dark, setDark] = useState(
JSON.parse(window.localStorage.getItem("isDark")!) ?? false
);
const [stars, setStars] = useState<number>(0);
const [gradientIndex, setGradientIndex] = useState<number>(0);
useEffect(() => {
async function fetchStars() {
const starsCount = await getRepoStars("logspace-ai", "langflow");
setStars(starsCount);
}
fetchStars();
const min = 0;
const max = 30;
setGradientIndex(Math.floor(Math.random() * (max - min + 1)) + min);
}, []);
useEffect(() => {
if (dark) {
document.getElementById("body")!.classList.add("dark");
} else {
document.getElementById("body")!.classList.remove("dark");
}
window.localStorage.setItem("isDark", dark.toString());
}, [dark]);
return (
<darkContext.Provider
value={{
setStars,
stars,
dark,
setDark,
setGradientIndex,
gradientIndex,
}}
>
{children}
</darkContext.Provider>
);
}

View file

@ -1,754 +0,0 @@
import { AxiosError } from "axios";
import _, { cloneDeep } from "lodash";
import {
ReactNode,
createContext,
useContext,
useEffect,
useRef,
useState,
} from "react";
import {
Edge,
Node,
ReactFlowJsonObject,
XYPosition,
addEdge,
} from "reactflow";
import ShortUniqueId from "short-unique-id";
import {
deleteFlowFromDatabase,
downloadFlowsFromDatabase,
getVersion,
readFlowsFromDatabase,
saveFlowToDatabase,
updateFlowInDatabase,
uploadFlowsToDatabase,
} from "../controllers/API";
import { APIClassType, APITemplateType } from "../types/api";
import { tweakType } from "../types/components";
import {
FlowType,
NodeDataType,
NodeType,
sourceHandleType,
targetHandleType,
} from "../types/flow";
import { FlowsContextType, FlowsState } from "../types/tabs";
import {
addVersionToDuplicates,
checkOldEdgesHandles,
createFlowComponent,
removeFileNameFromComponents,
scapeJSONParse,
scapedJSONStringfy,
updateEdgesHandleIds,
updateIds,
updateTemplate,
} from "../utils/reactflowUtils";
import {
createRandomKey,
getRandomDescription,
getRandomName,
} from "../utils/utils";
import { alertContext } from "./alertContext";
import { AuthContext } from "./authContext";
import { typesContext } from "./typesContext";
const uid = new ShortUniqueId({ length: 5 });
const FlowsContextInitialValue: FlowsContextType = {
tabId: "",
setTabId: (index: string) => {},
isLoading: true,
flows: [],
removeFlow: (id: string) => {},
addFlow: async (
newProject: boolean,
flowData?: FlowType,
override?: boolean
) => "",
updateFlow: (newFlow: FlowType) => {},
incrementNodeId: () => uid(),
downloadFlow: (flow: FlowType) => {},
downloadFlows: () => {},
uploadFlows: () => {},
uploadFlow: async () => "",
isBuilt: false,
setIsBuilt: (state: boolean) => {},
hardReset: () => {},
saveFlow: async (flow: FlowType, silent?: boolean) => {},
lastCopiedSelection: null,
setLastCopiedSelection: (selection: any) => {},
tabsState: {},
setTabsState: (state: FlowsState) => {},
saveCurrentFlow: () => {},
getNodeId: (nodeType: string) => "",
setTweak: (tweak: any) => {},
getTweak: [],
paste: (
selection: { nodes: any; edges: any },
position: { x: number; y: number; paneX?: number; paneY?: number }
) => {},
saveComponent: async (component: NodeDataType, override: boolean) => "",
deleteComponent: (key: string) => {},
version: "",
nodesOnFlow: "",
setNodesOnFlow: (nodes: string) => "",
};
export const FlowsContext = createContext<FlowsContextType>(
FlowsContextInitialValue
);
export function FlowsProvider({ children }: { children: ReactNode }) {
const { setErrorData, setNoticeData, setSuccessData } =
useContext(alertContext);
const { getAuthentication, isAuthenticated } = useContext(AuthContext);
const [tabId, setTabId] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [nodesOnFlow, setNodesOnFlow] = useState("");
const [flows, setFlows] = useState<Array<FlowType>>([]);
const [id, setId] = useState(uid());
const { reactFlowInstance, setData, data } = useContext(typesContext);
const [lastCopiedSelection, setLastCopiedSelection] = useState<{
nodes: any;
edges: any;
} | null>(null);
const [tabsState, setTabsState] = useState<FlowsState>({});
const [getTweak, setTweak] = useState<tweakType>([]);
useEffect(() => {
if (!isAuthenticated) {
hardReset();
}
}, [isAuthenticated]);
const newNodeId = useRef(uid());
function incrementNodeId() {
newNodeId.current = uid();
return newNodeId.current;
}
function refreshFlows() {
setIsLoading(true);
getTabsDataFromDB().then((DbData) => {
if (DbData) {
try {
processFlows(DbData, false);
updateStateWithDbData(DbData);
setIsLoading(false);
} catch (e) {}
}
});
}
useEffect(() => {
// If the user is authenticated, fetch the types. This code is important to check if the user is auth because of the execution order of the useEffect hooks.
if (getAuthentication() === true) {
// get data from db
refreshFlows();
}
}, [getAuthentication(), tabId]);
function getTabsDataFromDB() {
//get tabs from db
return readFlowsFromDatabase();
}
function processFlows(DbData: FlowType[], skipUpdate = true) {
let savedComponents: { [key: string]: APIClassType } = {};
DbData.forEach((flow: FlowType) => {
try {
if (!flow.data) {
return;
}
if (flow.data && flow.is_component) {
(flow.data.nodes[0].data as NodeDataType).node!.display_name =
flow.name;
savedComponents[
createRandomKey(
(flow.data.nodes[0].data as NodeDataType).type,
uid()
)
] = _.cloneDeep((flow.data.nodes[0].data as NodeDataType).node!);
return;
}
if (!skipUpdate) processDataFromFlow(flow, false);
} catch (e) {
console.log(e);
}
});
setData((prev) => {
let newData = cloneDeep(prev);
newData["saved_components"] = cloneDeep(savedComponents);
return newData;
});
}
function processFlowEdges(flow: FlowType) {
if (!flow.data || !flow.data.edges) return;
if (checkOldEdgesHandles(flow.data.edges)) {
const newEdges = updateEdgesHandleIds(flow.data);
flow.data.edges = newEdges;
}
//update edges colors
flow.data.edges.forEach((edge) => {
edge.className = "";
edge.style = { stroke: "#555" };
});
}
function updateDisplay_name(node: NodeType, template: APIClassType) {
node.data.node!.display_name = template["display_name"] || node.data.type;
}
function updateNodeDocumentation(node: NodeType, template: APIClassType) {
node.data.node!.documentation = template["documentation"];
}
function updateNodeBaseClasses(node: NodeType, template: APIClassType) {
node.data.node!.base_classes = template["base_classes"];
}
function updateNodeEdges(
flow: FlowType,
node: NodeType,
template: APIClassType
) {
flow.data!.edges.forEach((edge) => {
if (edge.source === node.id) {
let sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle!
);
sourceHandleObject.baseClasses = template["base_classes"];
edge.data.sourceHandle = sourceHandleObject;
edge.sourceHandle = scapedJSONStringfy(sourceHandleObject);
}
});
}
function updateNodeDescription(node: NodeType, template: APIClassType) {
node.data.node!.description = template["description"];
}
function updateNodeTemplate(node: NodeType, template: APIClassType) {
node.data.node!.template = updateTemplate(
template["template"] as unknown as APITemplateType,
node.data.node!.template as APITemplateType
);
}
function updateStateWithDbData(tabsData: FlowType[]) {
setFlows(tabsData);
}
function hardReset() {
newNodeId.current = uid();
setTabId("");
setFlows([]);
setIsLoading(true);
setId(uid());
}
/**
* Downloads the current flow as a JSON file
*/
function downloadFlow(
flow: FlowType,
flowName: string,
flowDescription?: string
) {
let clonedFlow = cloneDeep(flow);
removeFileNameFromComponents(clonedFlow);
// create a data URI with the current flow data
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify({
...clonedFlow,
name: flowName,
description: flowDescription,
})
)}`;
// create a link element and set its properties
const link = document.createElement("a");
link.href = jsonString;
link.download = `${
flowName && flowName != ""
? flowName
: flows.find((f) => f.id === tabId)!.name
}.json`;
// simulate a click on the link element to trigger the download
link.click();
}
function downloadFlows() {
downloadFlowsFromDatabase().then((flows) => {
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify(flows)
)}`;
// create a link element and set its properties
const link = document.createElement("a");
link.href = jsonString;
link.download = `flows.json`;
// simulate a click on the link element to trigger the download
link.click();
});
}
function getNodeId(nodeType: string) {
return nodeType + "-" + incrementNodeId();
}
/**
* 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.
* The resulting JSON object is passed to the addFlow function.
*/
async function uploadFlow({
newProject,
file,
isComponent = false,
position = { x: 10, y: 10 },
}: {
newProject: boolean;
file?: File;
isComponent?: boolean;
position?: XYPosition;
}): Promise<String | never> {
return new Promise(async (resolve, reject) => {
let id;
if (file) {
let text = await file.text();
let fileData = JSON.parse(text);
if (
newProject &&
((!fileData.is_component && isComponent === true) ||
(fileData.is_component !== undefined &&
fileData.is_component !== isComponent))
) {
reject("You cannot upload a component as a flow or vice versa");
} else {
if (fileData.flows) {
fileData.flows.forEach((flow: FlowType) => {
id = addFlow(newProject, flow, undefined, position);
});
resolve("");
} else {
id = await addFlow(newProject, fileData, undefined, position);
resolve(id);
}
}
} else {
// create a file input
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
// add a change event listener to the file input
input.onchange = async (e: Event) => {
if (
(e.target as HTMLInputElement).files![0].type === "application/json"
) {
const currentfile = (e.target as HTMLInputElement).files![0];
let text = await currentfile.text();
let fileData: FlowType = await JSON.parse(text);
if (
(!fileData.is_component && isComponent === true) ||
(fileData.is_component !== undefined &&
fileData.is_component !== isComponent)
) {
reject("You cannot upload a component as a flow or vice versa");
} else {
id = await addFlow(newProject, fileData);
resolve(id);
}
}
};
// trigger the file input click event to open the file dialog
input.click();
}
});
}
function uploadFlows() {
// create a file input
const input = document.createElement("input");
input.type = "file";
// add a change event listener to the file input
input.onchange = (event: Event) => {
// check if the file type is application/json
if (
(event.target as HTMLInputElement).files![0].type === "application/json"
) {
// get the file from the file input
const file = (event.target as HTMLInputElement).files![0];
// read the file as text
const formData = new FormData();
formData.append("file", file);
uploadFlowsToDatabase(formData).then(() => {
refreshFlows();
});
}
};
// trigger the file input click event to open the file dialog
input.click();
}
/**
* Removes a flow from an array of flows based on its id.
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
* @param {string} id - The id of the flow to remove.
*/
async function removeFlow(id: string) {
const index = flows.findIndex((flow) => flow.id === id);
if (index >= 0) {
await deleteFlowFromDatabase(id);
//removes component from data if there is any
setFlows(flows.filter((flow) => flow.id !== id));
processFlows(flows.filter((flow) => flow.id !== id));
}
}
/**
* Add a new flow to the list of flows.
* @param flow Optional flow to add.
*/
function paste(
selectionInstance: { nodes: Node[]; edges: Edge[] },
position: { x: number; y: number; paneX?: number; paneY?: number }
) {
let minimumX = Infinity;
let minimumY = Infinity;
let idsMap = {};
let nodes: Node<NodeDataType>[] = reactFlowInstance!.getNodes();
let edges = reactFlowInstance!.getEdges();
selectionInstance.nodes.forEach((node: Node) => {
if (node.position.y < minimumY) {
minimumY = node.position.y;
}
if (node.position.x < minimumX) {
minimumX = node.position.x;
}
});
const insidePosition = position.paneX
? { x: position.paneX + position.x, y: position.paneY! + position.y }
: reactFlowInstance!.screenToFlowPosition({
x: position.x,
y: position.y,
});
selectionInstance.nodes.forEach((node: NodeType) => {
// Generate a unique node ID
let newId = getNodeId(node.data.type);
idsMap[node.id] = newId;
// Create a new node object
const newNode: NodeType = {
id: newId,
type: "genericNode",
position: {
x: insidePosition.x + node.position!.x - minimumX,
y: insidePosition.y + node.position!.y - minimumY,
},
data: {
..._.cloneDeep(node.data),
id: newId,
},
};
// Add the new node to the list of nodes in state
nodes = nodes
.map((node) => ({ ...node, selected: false }))
.concat({ ...newNode, selected: false });
});
reactFlowInstance!.setNodes(nodes);
selectionInstance.edges.forEach((edge: Edge) => {
let source = idsMap[edge.source];
let target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle!
);
let sourceHandle = scapedJSONStringfy({
...sourceHandleObject,
id: source,
});
sourceHandleObject.id = source;
edge.data.sourceHandle = sourceHandleObject;
const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!
);
let targetHandle = scapedJSONStringfy({
...targetHandleObject,
id: target,
});
targetHandleObject.id = target;
edge.data.targetHandle = targetHandleObject;
let id =
"reactflow__edge-" +
source +
sourceHandle +
"-" +
target +
targetHandle;
edges = addEdge(
{
source,
target,
sourceHandle,
targetHandle,
id,
data: cloneDeep(edge.data),
style: { stroke: "#555" },
className:
targetHandleObject.type === "Text"
? "stroke-gray-800 "
: "stroke-gray-900 ",
animated: targetHandleObject.type === "Text",
selected: false,
},
edges.map((edge) => ({ ...edge, selected: false }))
);
});
reactFlowInstance!.setEdges(edges);
}
const addFlow = async (
newProject: Boolean,
flow?: FlowType,
override?: boolean,
position?: XYPosition
): Promise<String | undefined> => {
if (newProject) {
let flowData = flow
? processDataFromFlow(flow)
: { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
// Create a new flow with a default name if no flow is provided.
if (override) {
deleteComponent(flow!.name);
const newFlow = createNewFlow(flowData, flow!);
const { id } = await saveFlowToDatabase(newFlow);
newFlow.id = id;
//setTimeout to prevent update state with wrong state
setTimeout(() => {
addFlowToLocalState(newFlow);
}, 200);
// addFlowToLocalState(newFlow);
return;
}
const newFlow = createNewFlow(flowData, flow!);
const newName = addVersionToDuplicates(newFlow, flows);
newFlow.name = newName;
try {
const { id } = await saveFlowToDatabase(newFlow);
// Change the id to the new id.
newFlow.id = id;
// Add the new flow to the list of flows.
addFlowToLocalState(newFlow);
// Return the id
return id;
} catch (error) {
// Handle the error if needed
throw error; // Re-throw the error so the caller can handle it if needed
}
} else {
paste(
{ nodes: flow!.data!.nodes, edges: flow!.data!.edges },
position ?? { x: 10, y: 10 }
);
}
};
const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
let data = flow?.data ? flow.data : null;
if (data) {
processFlowEdges(flow);
//prevent node update for now
// processFlowNodes(flow);
//add animation to text type edges
updateEdges(data.edges);
// updateNodes(data.nodes, data.edges);
if (refreshIds) updateIds(data, getNodeId); // Assuming updateIds is defined elsewhere
}
return data;
};
const updateEdges = (edges: Edge[]) => {
if (edges)
edges.forEach((edge) => {
const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!
);
edge.className =
(targetHandleObject.type === "Text"
? "stroke-gray-800 "
: "stroke-gray-900 ") + " stroke-connection";
edge.animated = targetHandleObject.type === "Text";
});
};
const createNewFlow = (
flowData: ReactFlowJsonObject | null,
flow: FlowType
) => ({
description: flow?.description ?? getRandomDescription(),
name: flow?.name ?? getRandomName(),
data: flowData,
id: "",
is_component: flow?.is_component ?? false,
});
const addFlowToLocalState = (newFlow: FlowType) => {
let newFlows: FlowType[] = [];
setFlows((prevState) => {
newFlows = newFlows.concat(prevState);
newFlows.push(newFlow);
return [...prevState, newFlow];
});
processFlows(newFlows);
};
/**
* 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;
}
newFlow = {
...newFlow,
};
return newFlows;
});
}
function saveCurrentFlow() {
const currentFlow = flows.find((flow) => flow.id === tabId);
if (currentFlow && reactFlowInstance && currentFlow.data) {
updateFlow({ ...currentFlow, data: reactFlowInstance?.toObject()! });
}
}
async function saveFlow(newFlow: FlowType, silent?: boolean) {
try {
// updates flow in db
const updatedFlow = await updateFlowInDatabase(newFlow);
if (updatedFlow) {
// updates flow in state
if (!silent) {
setSuccessData({ title: "Changes saved successfully" });
}
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;
});
//update tabs state
setTabsState((prev) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: false,
},
};
});
}
} catch (err) {
setErrorData({
title: "Error while saving changes",
list: [(err as AxiosError).message],
});
}
}
function saveComponent(component: NodeDataType, override: boolean) {
component.node!.official = false;
return addFlow(true, createFlowComponent(component, version), override);
}
function deleteComponent(key: string) {
let componentFlow = flows.find(
(componentFlow) =>
componentFlow.is_component && componentFlow.name === key
);
if (componentFlow) {
removeFlow(componentFlow.id);
}
}
const [isBuilt, setIsBuilt] = useState(false);
// Initialize state variable for the version
const [version, setVersion] = useState("");
useEffect(() => {
getVersion().then((data) => {
setVersion(data.version);
});
}, []);
return (
<FlowsContext.Provider
value={{
version,
saveFlow,
isBuilt,
setIsBuilt,
lastCopiedSelection,
setLastCopiedSelection,
saveCurrentFlow,
hardReset,
tabId,
setTabId,
flows,
incrementNodeId,
removeFlow,
addFlow,
updateFlow,
downloadFlow,
downloadFlows,
uploadFlows,
uploadFlow,
getNodeId,
tabsState,
setTabsState,
paste,
getTweak,
setTweak,
isLoading,
saveComponent,
deleteComponent,
nodesOnFlow,
setNodesOnFlow,
}}
>
{children}
</FlowsContext.Provider>
);
}

View file

@ -3,44 +3,21 @@ import { BrowserRouter } from "react-router-dom";
import { ReactFlowProvider } from "reactflow";
import { TooltipProvider } from "../components/ui/tooltip";
import { ApiInterceptor } from "../controllers/API/api";
import { SSEProvider } from "./SSEContext";
import { AlertProvider } from "./alertContext";
import { AuthProvider } from "./authContext";
import { DarkProvider } from "./darkContext";
import { FlowsProvider } from "./flowsContext";
import { LocationProvider } from "./locationContext";
import { StoreProvider } from "./storeContext";
import { TypesProvider } from "./typesContext";
import { UndoRedoProvider } from "./undoRedoContext";
export default function ContextWrapper({ children }: { children: ReactNode }) {
//element to wrap all context
return (
<>
<BrowserRouter>
<AlertProvider>
<AuthProvider>
<TooltipProvider>
<ReactFlowProvider>
<DarkProvider>
<TypesProvider>
<LocationProvider>
<ApiInterceptor />
<SSEProvider>
<FlowsProvider>
<UndoRedoProvider>
<StoreProvider>{children}</StoreProvider>
</UndoRedoProvider>
</FlowsProvider>
</SSEProvider>
</LocationProvider>
</TypesProvider>
</DarkProvider>
</ReactFlowProvider>
</TooltipProvider>
</AuthProvider>
</AlertProvider>
<AuthProvider>
<TooltipProvider>
<ReactFlowProvider>
<ApiInterceptor />
{children}
</ReactFlowProvider>
</TooltipProvider>
</AuthProvider>
</BrowserRouter>
</>
);

View file

@ -1,50 +0,0 @@
import { createContext, ReactNode, useState } from "react";
import { locationContextType } from "../types/typesContext";
//initial value for location context
const initialValue = {
//actual
current: window.location.pathname.replace(/\/$/g, "").split("/"),
isStackedOpen:
window.innerWidth > 1024 && window.location.pathname.split("/")[1]
? true
: false,
setCurrent: () => {},
setIsStackedOpen: () => {},
showSideBar: window.location.pathname.split("/")[1] ? true : false,
setShowSideBar: () => {},
extraNavigation: { title: "" },
setExtraNavigation: () => {},
extraComponent: <></>,
setExtraComponent: () => {},
};
export const locationContext = createContext<locationContextType>(initialValue);
export function LocationProvider({ children }: { children: ReactNode }) {
const [current, setCurrent] = useState(initialValue.current);
const [isStackedOpen, setIsStackedOpen] = useState(
initialValue.isStackedOpen
);
const [showSideBar, setShowSideBar] = useState(initialValue.showSideBar);
const [extraNavigation, setExtraNavigation] = useState({ title: "" });
const [extraComponent, setExtraComponent] = useState(<></>);
return (
<locationContext.Provider
value={{
isStackedOpen,
setIsStackedOpen,
current,
setCurrent,
showSideBar,
setShowSideBar,
extraNavigation,
setExtraNavigation,
extraComponent,
setExtraComponent,
}}
>
{children}
</locationContext.Provider>
);
}

View file

@ -1,74 +0,0 @@
import { createContext, useContext, useEffect, useState } from "react";
import { checkHasApiKey, checkHasStore } from "../controllers/API";
import { storeContextType } from "../types/contexts/store";
import { AuthContext } from "./authContext";
//store context to share user components and update them
const initialValue = {
hasStore: true,
setHasStore: () => {},
validApiKey: false,
setValidApiKey: () => {},
hasApiKey: false,
setHasApiKey: () => {},
loadingApiKey: true,
};
export const StoreContext = createContext<storeContextType>(initialValue);
export function StoreProvider({ children }) {
const [hasStore, setHasStore] = useState(false);
const [loadingApiKey, setLoadingApiKey] = useState(true);
const [hasApiKey, setHasApiKey] = useState(true);
const [validApiKey, setValidApiKey] = useState(false);
const [storeChecked, setStoreChecked] = useState(false);
const { apiKey } = useContext(AuthContext);
useEffect(() => {
const fetchStoreData = async () => {
try {
if (storeChecked) return;
const res = await checkHasStore();
setHasStore(res?.enabled ?? false);
setStoreChecked(true);
} catch (e) {
console.log(e);
}
};
fetchStoreData();
}, []);
const fetchApiData = async () => {
setLoadingApiKey(true);
try {
const res = await checkHasApiKey();
setHasApiKey(res?.has_api_key ?? false);
setValidApiKey(res?.is_valid ?? false);
setLoadingApiKey(false);
} catch (e) {
setLoadingApiKey(false);
console.log(e);
}
};
useEffect(() => {
fetchApiData();
}, [storeChecked, apiKey]);
return (
<StoreContext.Provider
value={{
hasStore,
setHasStore,
hasApiKey,
setHasApiKey,
validApiKey,
setValidApiKey,
loadingApiKey,
}}
>
{children}
</StoreContext.Provider>
);
}

View file

@ -1,144 +0,0 @@
import _ from "lodash";
import {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from "react";
import { ReactFlowInstance } from "reactflow";
import { getAll, getHealth } from "../controllers/API";
import { APIKindType } from "../types/api";
import { typesContextType } from "../types/typesContext";
import { alertContext } from "./alertContext";
import { AuthContext } from "./authContext";
//context to share types adn functions from nodes to flow
const initialValue: typesContextType = {
reactFlowInstance: null,
setReactFlowInstance: (newState: ReactFlowInstance) => {},
deleteNode: () => {},
types: {},
setTypes: () => {},
templates: {},
setTemplates: () => {},
data: {},
setData: () => {},
setFetchError: () => {},
fetchError: false,
setFilterEdge: (filter) => {},
getFilterEdge: [],
deleteEdge: () => {},
};
export const typesContext = createContext<typesContextType>(initialValue);
export function TypesProvider({ children }: { children: ReactNode }) {
const [types, setTypes] = useState({});
const [reactFlowInstance, setReactFlowInstance] =
useState<ReactFlowInstance | null>(null);
const [templates, setTemplates] = useState({});
const [data, setData] = useState({});
const [fetchError, setFetchError] = useState(false);
const { setLoading } = useContext(alertContext);
const { getAuthentication } = useContext(AuthContext);
const [getFilterEdge, setFilterEdge] = useState([]);
useEffect(() => {
// If the user is authenticated, fetch the types. This code is important to check if the user is auth because of the execution order of the useEffect hooks.
if (getAuthentication() === true) {
getTypes();
}
}, [getAuthentication()]);
async function getTypes(): Promise<void> {
// We will keep a flag to handle the case where the component is unmounted before the API call resolves.
let isMounted = true;
try {
const result = await getAll();
// Make sure to only update the state if the component is still mounted.
if (isMounted && result?.status === 200) {
setLoading(false);
let { data } = _.cloneDeep(result);
setData((old) => ({ ...old, ...data }));
setTemplates(
Object.keys(data).reduce((acc, curr) => {
Object.keys(data[curr]).forEach((c: keyof APIKindType) => {
//prevent wrong overwriting of the component template by a group of the same type
if (!data[curr][c].flow) acc[c] = data[curr][c];
});
return acc;
}, {})
);
// Set the types by reducing over the keys of the result data and updating the accumulator.
setTypes(
// Reverse the keys so the tool world does not overlap
Object.keys(data)
.reverse()
.reduce((acc, curr) => {
Object.keys(data[curr]).forEach((c: keyof APIKindType) => {
acc[c] = curr;
// Add the base classes to the accumulator as well.
data[curr][c].base_classes?.forEach((b) => {
acc[b] = curr;
});
});
return acc;
}, {})
);
}
} catch (error) {
console.error("An error has occurred while fetching types.");
console.log(error);
await getHealth().catch((e) => {
setFetchError(true);
});
}
}
function deleteNode(idx: string | Array<string>) {
if (reactFlowInstance === null) return;
const edges = reactFlowInstance!
.getEdges()
.filter((edge) =>
typeof idx === "string"
? edge.source == idx || edge.target == idx
: idx.includes(edge.source) || idx.includes(edge.target)
);
reactFlowInstance!.deleteElements({
nodes:
typeof idx === "string" ? [{ id: idx }] : idx.map((id) => ({ id })),
edges,
});
}
function deleteEdge(idx: string | Array<string>) {
reactFlowInstance!.deleteElements({
edges:
typeof idx === "string" ? [{ id: idx }] : idx.map((id) => ({ id })),
});
}
return (
<typesContext.Provider
value={{
deleteEdge,
types,
setTypes,
reactFlowInstance,
setReactFlowInstance,
deleteNode,
setTemplates,
templates,
data,
setData,
fetchError,
setFetchError,
setFilterEdge,
getFilterEdge,
}}
>
{children}
</typesContext.Provider>
);
}

View file

@ -1,188 +0,0 @@
import { cloneDeep } from "lodash";
import {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import { useReactFlow } from "reactflow";
import {
HistoryItem,
UseUndoRedoOptions,
undoRedoContextType,
} from "../types/typesContext";
import { isWrappedWithClass } from "../utils/utils";
import { FlowsContext } from "./flowsContext";
const initialValue = {
undo: () => {},
redo: () => {},
takeSnapshot: () => {},
};
const defaultOptions: UseUndoRedoOptions = {
maxHistorySize: 100,
enableShortcuts: true,
};
export const undoRedoContext = createContext<undoRedoContextType>(initialValue);
export function UndoRedoProvider({ children }) {
const { tabId, flows } = useContext(FlowsContext);
const [past, setPast] = useState<HistoryItem[][]>(flows.map(() => []));
const [future, setFuture] = useState<HistoryItem[][]>(flows.map(() => []));
const [tabIndex, setTabIndex] = useState(
flows.findIndex((flow) => flow.id === tabId)
);
useEffect(() => {
// whenever the flows variable changes, we need to add one array to the past and future states
setPast((old) =>
flows.map((flow, index) => (old[index] ? old[index] : []))
);
setFuture((old) =>
flows.map((flow, index) => (old[index] ? old[index] : []))
);
setTabIndex(flows.findIndex((flow) => flow.id === tabId));
}, [flows, tabId]);
const { setNodes, setEdges, getNodes, getEdges } = useReactFlow();
const takeSnapshot = useCallback(() => {
// push the current graph to the past state
let newPast = cloneDeep(past);
let newState = {
nodes: cloneDeep(getNodes()),
edges: cloneDeep(getEdges()),
};
if (
past[tabIndex] &&
JSON.stringify(past[tabIndex][past[tabIndex].length - 1]) !==
JSON.stringify(newState)
) {
newPast[tabIndex] = past[tabIndex].slice(
past[tabIndex].length - defaultOptions.maxHistorySize + 1,
past[tabIndex].length
);
newPast[tabIndex].push(newState);
}
setPast(newPast);
// whenever we take a new snapshot, the redo operations need to be cleared to avoid state mismatches
setFuture((old) => {
let newFuture = cloneDeep(old);
newFuture[tabIndex] = [];
return newFuture;
});
}, [getNodes, getEdges, past, future, flows, tabId, setPast, setFuture]);
const undo = useCallback(() => {
// get the last state that we want to go back to
const pastState = past[tabIndex][past[tabIndex].length - 1];
if (pastState) {
// first we remove the state from the history
setPast((old) => {
let newPast = cloneDeep(old);
newPast[tabIndex] = old[tabIndex].slice(0, old[tabIndex].length - 1);
return newPast;
});
// we store the current graph for the redo operation
setFuture((old) => {
let newFuture = cloneDeep(old);
newFuture[tabIndex] = old[tabIndex];
newFuture[tabIndex].push({ nodes: getNodes(), edges: getEdges() });
return newFuture;
});
// now we can set the graph to the past state
setNodes(pastState.nodes);
setEdges(pastState.edges);
}
}, [
setNodes,
setEdges,
getNodes,
getEdges,
future,
past,
setFuture,
setPast,
tabIndex,
]);
const redo = useCallback(() => {
const futureState = future[tabIndex][future[tabIndex].length - 1];
if (futureState) {
setFuture((old) => {
let newFuture = cloneDeep(old);
newFuture[tabIndex] = old[tabIndex].slice(0, old[tabIndex].length - 1);
return newFuture;
});
setPast((old) => {
let newPast = cloneDeep(old);
newPast[tabIndex] = old[tabIndex];
newPast[tabIndex].push({ nodes: getNodes(), edges: getEdges() });
return newPast;
});
setNodes(futureState.nodes);
setEdges(futureState.edges);
}
}, [
future,
past,
setFuture,
setPast,
setNodes,
setEdges,
getNodes,
getEdges,
future,
tabIndex,
]);
useEffect(() => {
// this effect is used to attach the global event handlers
if (!defaultOptions.enableShortcuts) {
return;
}
const keyDownHandler = (event: KeyboardEvent) => {
if (!isWrappedWithClass(event, "noundo")) {
if (
event.key === "z" &&
(event.ctrlKey || event.metaKey) &&
event.shiftKey
) {
event.preventDefault();
redo();
} else if (event.key === "y" && (event.ctrlKey || event.metaKey)) {
event.preventDefault(); // prevent the default action
redo();
} else if (event.key === "z" && (event.ctrlKey || event.metaKey)) {
event.preventDefault();
undo();
}
}
};
document.addEventListener("keydown", keyDownHandler);
return () => {
document.removeEventListener("keydown", keyDownHandler);
};
}, [undo, redo]);
return (
<undoRedoContext.Provider
value={{
undo,
redo,
takeSnapshot,
}}
>
{children}
</undoRedoContext.Provider>
);
}

View file

@ -3,8 +3,8 @@ import { useContext, useEffect } from "react";
import { Cookies } from "react-cookie";
import { useNavigate } from "react-router-dom";
import { renewAccessToken } from ".";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import useAlertStore from "../../stores/alertStore";
// Create a new Axios instance
const api: AxiosInstance = axios.create({
@ -12,8 +12,8 @@ const api: AxiosInstance = axios.create({
});
function ApiInterceptor() {
const { setErrorData } = useContext(alertContext);
let { accessToken, login, logout, authenticationErrorCount } =
const setErrorData = useAlertStore((state) => state.setErrorData);
let { accessToken, login, logout, authenticationErrorCount, autoLogin } =
useContext(AuthContext);
const navigate = useNavigate();
const cookies = new Cookies();
@ -23,23 +23,22 @@ function ApiInterceptor() {
(response) => response,
async (error: AxiosError) => {
if (error.response?.status === 401) {
const refreshToken = cookies.get("refresh_tkn_lflw");
if (refreshToken && refreshToken !== "auto") {
const accessToken = cookies.get("access_token_lf");
if (accessToken && !autoLogin) {
authenticationErrorCount = authenticationErrorCount + 1;
if (authenticationErrorCount > 3) {
authenticationErrorCount = 0;
logout();
navigate("/login");
}
try {
const res = await renewAccessToken(refreshToken);
const res = await renewAccessToken();
if (res?.data?.access_token && res?.data?.refresh_token) {
login(res?.data?.access_token, res?.data?.refresh_token);
login(res?.data?.access_token);
}
if (error?.config?.headers) {
delete error.config.headers["Authorization"];
error.config.headers["Authorization"] = `Bearer ${cookies.get(
"access_tkn_lflw"
"access_token_lf"
)}`;
const response = await axios.request(error.config);
return response;
@ -47,20 +46,17 @@ function ApiInterceptor() {
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 401) {
logout();
navigate("/login");
} else {
console.error(error);
logout();
navigate("/login");
}
}
}
if (!refreshToken && error?.config?.url?.includes("login")) {
if (!accessToken && error?.config?.url?.includes("login")) {
return Promise.reject(error);
} else {
logout();
navigate("/login");
}
} else {
// if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
@ -100,6 +96,7 @@ function ApiInterceptor() {
// Request interceptor to add access token to every request
const requestInterceptor = api.interceptors.request.use(
(config) => {
const accessToken = cookies.get("access_token_lf");
if (accessToken && !isAuthorizedURL(config?.url)) {
config.headers["Authorization"] = `Bearer ${accessToken}`;
}

View file

@ -297,8 +297,8 @@ export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
* @returns {Promise<AxiosResponse<any>>} A promise that resolves to an AxiosResponse containing the version information.
*/
export async function getVersion() {
const respnose = await api.get(`${BASE_URL_API}version`);
return respnose.data;
const response = await api.get(`${BASE_URL_API}version`);
return response.data;
}
/**
@ -411,11 +411,9 @@ export async function autoLogin() {
}
}
export async function renewAccessToken(token: string) {
export async function renewAccessToken() {
try {
if (token) {
return await api.post(`${BASE_URL_API}refresh?token=${token}`);
}
return await api.post(`${BASE_URL_API}refresh`);
} catch (error) {
throw error;
}
@ -429,7 +427,6 @@ export async function getLoggedUser(): Promise<Users | null> {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
return null;
@ -443,7 +440,6 @@ export async function addUser(user: UserInputType): Promise<Array<Users>> {
}
return res.data;
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -460,7 +456,6 @@ export async function getUsersPage(
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
return [];
@ -473,7 +468,6 @@ export async function deleteUser(user_id: string) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -485,7 +479,6 @@ export async function updateUser(user_id: string, user: changeUser) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -500,7 +493,6 @@ export async function resetPassword(user_id: string, user: resetPasswordType) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -512,7 +504,6 @@ export async function getApiKey() {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -524,7 +515,6 @@ export async function createApiKey(name: string) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -536,7 +526,6 @@ export async function deleteApiKey(api_key: string) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -550,7 +539,6 @@ export async function addApiKeyStore(key: string) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -684,7 +672,6 @@ export async function getStoreComponents({
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -696,7 +683,6 @@ export async function postStoreComponents(component: Component) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -710,7 +696,6 @@ export async function getComponent(component_id: string) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -750,7 +735,6 @@ export async function searchComponent(
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -762,7 +746,6 @@ export async function checkHasApiKey() {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -774,7 +757,6 @@ export async function checkHasStore() {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -797,7 +779,6 @@ export async function getCountComponents(is_component?: boolean | null) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -809,7 +790,6 @@ export async function getStoreTags() {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
@ -860,3 +840,13 @@ export async function updateFlowStore(
throw error;
}
}
export async function requestLogout() {
try {
const response = await api.post(`${BASE_URL_API}logout`);
return response.data;
} catch (error) {
console.error(error);
throw error;
}
}

View file

@ -18,7 +18,7 @@ import {
LANGFLOW_SUPPORTED_TYPES,
} from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import useFlowStore from "../../stores/flowStore";
import { TemplateVariableType } from "../../types/api";
import { tweakType, uniqueTweakType } from "../../types/components";
import { FlowType, NodeType } from "../../types/flow/index";
@ -48,16 +48,17 @@ const ApiModal = forwardRef(
const [activeTab, setActiveTab] = useState("0");
const tweak = useRef<tweakType>([]);
const tweaksList = useRef<string[]>([]);
const { setTweak, getTweak, tabsState } = useContext(FlowsContext);
const [getTweak, setTweak] = useState<tweakType>([]);
const flowState = useFlowStore((state) => state.flowState);
const pythonApiCode = getPythonApiCode(
flow,
autoLogin,
tweak.current,
tabsState
flowState
);
const curl_code = getCurlCode(flow, autoLogin, tweak.current, tabsState);
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
const widgetCode = getWidgetCode(flow, autoLogin, tabsState);
const curl_code = getCurlCode(flow, autoLogin, tweak.current, flowState);
const pythonCode = getPythonCode(flow, tweak.current, flowState);
const widgetCode = getWidgetCode(flow, autoLogin, flowState);
const tweaksCode = buildTweaks(flow);
const codesArray = [
curl_code,
@ -168,11 +169,11 @@ const ApiModal = forwardRef(
flow,
autoLogin,
tweak.current,
tabsState
flowState
);
const curl_code = getCurlCode(flow, autoLogin, tweak.current, tabsState);
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
const widgetCode = getWidgetCode(flow, autoLogin, tabsState);
const curl_code = getCurlCode(flow, autoLogin, tweak.current, flowState);
const pythonCode = getPythonCode(flow, tweak.current, flowState);
const widgetCode = getWidgetCode(flow, autoLogin, flowState);
tabs![0].code = curl_code;
tabs![1].code = pythonApiCode;

View file

@ -1,20 +1,24 @@
import React, { useEffect, useState } from "react";
import ShadTooltip from "../../components/ShadTooltipComponent";
import { Button } from "../../components/ui/button";
import { ConfirmationModalType, ContentProps } from "../../types/components";
import {
ConfirmationModalType,
ContentProps,
TriggerProps,
} from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils";
import BaseModal from "../baseModal";
const Content: React.FC<ContentProps> = ({ children }) => {
return <div className="h-full w-full">{children}</div>;
};
const Trigger: React.FC<ContentProps> = ({
const Trigger: React.FC<TriggerProps> = ({
children,
tolltipContent,
tooltipContent,
side,
}) => {
return tolltipContent ? (
<ShadTooltip side={side} content={tolltipContent}>
}: TriggerProps) => {
return tooltipContent ? (
<ShadTooltip side={side} content={tooltipContent}>
<div className="h-full w-full">{children}</div>
</ShadTooltip>
) : (
@ -23,7 +27,6 @@ const Trigger: React.FC<ContentProps> = ({
};
function ConfirmationModal({
title,
asChild,
titleHeader,
modalContentTitle,
cancelText,
@ -49,6 +52,7 @@ function ConfirmationModal({
useEffect(() => {
if (onClose) onClose!(modalOpen);
}, [modalOpen]);
const triggerChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Trigger
);
@ -58,7 +62,7 @@ function ConfirmationModal({
return (
<BaseModal size={size} open={modalOpen} setOpen={setModalOpen}>
<BaseModal.Trigger asChild={asChild}>{triggerChild}</BaseModal.Trigger>
<BaseModal.Trigger>{triggerChild}</BaseModal.Trigger>
<BaseModal.Header description={titleHeader ?? null}>
<span className="pr-2">{title}</span>
<Icon

View file

@ -1,6 +1,5 @@
import { cloneDeep } from "lodash";
import { forwardRef, useContext, useEffect, useState } from "react";
import { useUpdateNodeInternals } from "reactflow";
import { forwardRef, useEffect, useState } from "react";
import ShadTooltip from "../../components/ShadTooltipComponent";
import CodeAreaComponent from "../../components/codeAreaComponent";
import DictComponent from "../../components/dictComponent";
@ -29,11 +28,8 @@ import {
LANGFLOW_SUPPORTED_TYPES,
limitScrollFieldsModal,
} from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { typesContext } from "../../contexts/typesContext";
import useFlowStore from "../../stores/flowStore";
import { NodeDataType } from "../../types/flow";
import { FlowsState } from "../../types/tabs";
import {
convertObjToArray,
convertValuesToNumbers,
@ -58,13 +54,11 @@ const EditNodeModal = forwardRef(
},
ref
) => {
const updateNodeInternals = useUpdateNodeInternals();
const [myData, setMyData] = useState(data);
const { setTabsState, tabId } = useContext(FlowsContext);
const { reactFlowInstance } = useContext(typesContext);
const { setModalContextOpen } = useContext(alertContext);
const setPending = useFlowStore((state) => state.setPending);
const edges = useFlowStore((state) => state.edges);
const setNode = useFlowStore((state) => state.setNode);
function changeAdvanced(n) {
setMyData((old) => {
@ -81,14 +75,12 @@ const EditNodeModal = forwardRef(
newData.node!.template[name].value = newValue;
return newData;
});
updateNodeInternals(data.id);
};
useEffect(() => {
if (open) {
setMyData(data); // reset data to what it is on node when opening modal
}
setModalContextOpen(open);
}, [open]);
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
@ -159,7 +151,7 @@ const EditNodeModal = forwardRef(
fieldName: templateParam,
};
let disabled =
reactFlowInstance?.getEdges().some(
edges.some(
(edge) =>
edge.targetHandle ===
scapedJSONStringfy(
@ -224,8 +216,16 @@ const EditNodeModal = forwardRef(
) : myData.node.template[templateParam]
.multiline ? (
<TextAreaComponent
id={"textarea-edit-" + index}
data-testid={"textarea-edit-" + index}
id={
"textarea-edit-" +
myData.node.template[templateParam]
.name
}
data-testid={
"textarea-edit-" +
myData.node.template[templateParam]
.name
}
disabled={disabled}
editNode={true}
value={
@ -456,9 +456,13 @@ const EditNodeModal = forwardRef(
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
id={"prompt-area-edit" + index}
id={
"prompt-area-edit-" +
myData.node.template[templateParam].name
}
data-testid={
"modal-prompt-input-" + index
"modal-prompt-input-" +
myData.node.template[templateParam].name
}
/>
</div>
@ -535,17 +539,14 @@ const EditNodeModal = forwardRef(
id={"saveChangesBtn"}
className="mt-3"
onClick={() => {
data.node = myData.node;
//@ts-ignore
setTabsState((prev: FlowsState) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
setNode(data.id, (old) => ({
...old,
data: {
...old.data,
node: myData.node,
},
}));
setPending(true);
setOpen(false);
}}
type="submit"

View file

@ -1,10 +1,10 @@
import * as Form from "@radix-ui/react-form";
import { useContext, useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { alertContext } from "../../contexts/alertContext";
import { createApiKey } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import { ApiKeyType } from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils";
import BaseModal from "../baseModal";
@ -24,7 +24,7 @@ export default function SecretKeyModal({
const [apiKeyValue, setApiKeyValue] = useState("");
const [renderKey, setRenderKey] = useState(false);
const [textCopied, setTextCopied] = useState(true);
const { setSuccessData } = useContext(alertContext);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const inputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {

View file

@ -3,10 +3,10 @@ import { useContext, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { StoreContext } from "../../contexts/storeContext";
import { addApiKeyStore } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import { useStoreStore } from "../../stores/storeStore";
import { StoreApiKeyType } from "../../types/components";
import BaseModal from "../baseModal";
@ -16,11 +16,17 @@ export default function StoreApiKeyModal({
}: StoreApiKeyType) {
if (disabled) return <>{children}</>;
const [open, setOpen] = useState(false);
const { setSuccessData, setErrorData } = useContext(alertContext);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const { storeApiKey } = useContext(AuthContext);
const { hasApiKey, validApiKey } = useContext(StoreContext);
const [apiKeyValue, setApiKeyValue] = useState("");
const validApiKey = useStoreStore((state) => state.validApiKey);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
const setHasApiKey = useStoreStore((state) => state.updateHasApiKey);
const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
const setLoadingApiKey = useStoreStore((state) => state.updateLoadingApiKey);
const handleSaveKey = () => {
if (apiKeyValue) {
addApiKeyStore(apiKeyValue).then(
@ -30,12 +36,18 @@ export default function StoreApiKeyModal({
});
storeApiKey(apiKeyValue);
setOpen(false);
setHasApiKey(true);
setValidApiKey(true);
setLoadingApiKey(false);
},
(error) => {
setErrorData({
title: "There was an error saving the API Key, please try again.",
list: [error["response"]["data"]["detail"]],
});
setHasApiKey(false);
setValidApiKey(false);
setLoadingApiKey(false);
}
);
}

View file

@ -28,6 +28,7 @@ const Trigger: React.FC<TriggerProps> = ({ children, asChild, disable }) => {
<DialogTrigger
className={asChild ? "" : "w-full"}
hidden={children ? false : true}
disabled={disable}
asChild={asChild}
>
{children}

View file

@ -4,16 +4,15 @@ import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/theme-twilight";
// import "ace-builds/webpack-resolver";
import { useContext, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import AceEditor from "react-ace";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext";
import { typesContext } from "../../contexts/typesContext";
import { postCustomComponent, postValidateCode } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore";
import { codeAreaModalPropsType } from "../../types/components";
import BaseModal from "../baseModal";
@ -27,10 +26,11 @@ export default function CodeAreaModal({
readonly = false,
}: codeAreaModalPropsType): JSX.Element {
const [code, setCode] = useState(value);
const { dark } = useContext(darkContext);
const { reactFlowInstance } = useContext(typesContext);
const dark = useDarkStore((state) => state.dark);
const [height, setHeight] = useState<string | null>(null);
const { setErrorData, setSuccessData } = useContext(alertContext);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const [error, setError] = useState<{
detail: { error: string | undefined; traceback: string | undefined };
} | null>(null);

View file

@ -1,33 +1,32 @@
import { ReactNode, forwardRef, useContext, useEffect, useState } from "react";
import { ReactNode, forwardRef, useEffect, useState } from "react";
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox";
import { EXPORT_DIALOG_SUBTITLE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { typesContext } from "../../contexts/typesContext";
import { removeApiKeys } from "../../utils/reactflowUtils";
import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { downloadFlow, removeApiKeys } from "../../utils/reactflowUtils";
import BaseModal from "../baseModal";
const ExportModal = forwardRef(
(props: { children: ReactNode }, ref): JSX.Element => {
const { flows, tabId, downloadFlow, version } = useContext(FlowsContext);
const { reactFlowInstance } = useContext(typesContext);
const { setNoticeData } = useContext(alertContext);
const version = useDarkStore((state) => state.version);
const setNoticeData = useAlertStore((state) => state.setNoticeData);
const [checked, setChecked] = useState(true);
const flow = flows.find((f) => f.id === tabId);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
useEffect(() => {
setName(flow!.name);
setDescription(flow!.description);
}, [flow!.name, flow!.description]);
const [name, setName] = useState(flow!.name);
const [description, setDescription] = useState(flow!.description);
setName(currentFlow!.name);
setDescription(currentFlow!.description);
}, [currentFlow!.name, currentFlow!.description]);
const [name, setName] = useState(currentFlow!.name);
const [description, setDescription] = useState(currentFlow!.description);
const [open, setOpen] = useState(false);
return (
<BaseModal size="smaller-h-full" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{props.children}</BaseModal.Trigger>
<BaseModal.Trigger asChild>{props.children}</BaseModal.Trigger>
<BaseModal.Header description={EXPORT_DIALOG_SUBTITLE}>
<span className="pr-2">Export</span>
<IconComponent
@ -67,8 +66,8 @@ const ExportModal = forwardRef(
if (checked) {
downloadFlow(
{
id: tabId,
data: flow!.data!,
id: currentFlow!.id,
data: currentFlow!.data!,
description,
name,
last_tested_version: version,
@ -84,8 +83,8 @@ const ExportModal = forwardRef(
} else
downloadFlow(
removeApiKeys({
id: tabId,
data: flow!.data!,
id: currentFlow!.id,
data: currentFlow!.data!,
description,
name,
last_tested_version: version,

View file

@ -1,9 +1,9 @@
import { useContext, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
import { FlowsContext } from "../../contexts/flowsContext";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { FlowSettingsPropsType } from "../../types/components";
import { FlowType } from "../../types/flow";
import BaseModal from "../baseModal";
@ -12,20 +12,21 @@ export default function FlowSettingsModal({
open,
setOpen,
}: FlowSettingsPropsType): JSX.Element {
const { flows, tabId, saveFlow } = useContext(FlowsContext);
const flow = flows.find((f) => f.id === tabId);
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const flows = useFlowsManagerStore((state) => state.flows);
useEffect(() => {
setName(flow!.name);
setDescription(flow!.description);
}, [flow!.name, flow!.description]);
const [name, setName] = useState(flow!.name);
const [description, setDescription] = useState(flow!.description);
setName(currentFlow!.name);
setDescription(currentFlow!.description);
}, [currentFlow!.name, currentFlow!.description, open]);
const [name, setName] = useState(currentFlow!.name);
const [description, setDescription] = useState(currentFlow!.description);
function handleClick(): void {
let savedFlow = flows.find((flow) => flow.id === tabId);
savedFlow!.name = name;
savedFlow!.description = description;
saveFlow(savedFlow!);
currentFlow!.name = name;
currentFlow!.description = description;
saveFlow(currentFlow!);
setOpen(false);
}
@ -36,7 +37,7 @@ export default function FlowSettingsModal({
flows.forEach((flow: FlowType) => {
if ((flow.is_component ?? false) === false) tempNameList.push(flow.name);
});
setNameList(tempNameList.filter((name) => name !== flow!.name));
setNameList(tempNameList.filter((name) => name !== currentFlow!.name));
}, [flows]);
return (
@ -57,7 +58,7 @@ export default function FlowSettingsModal({
<BaseModal.Footer>
<Button
disabled={nameLists.includes(name) && name !== flow!.name}
disabled={nameLists.includes(name) && name !== currentFlow!.name}
onClick={handleClick}
type="submit"
>

View file

@ -1,6 +1,4 @@
import { useContext, useEffect, useRef, useState } from "react";
import { alertContext } from "../../contexts/alertContext";
import { typesContext } from "../../contexts/typesContext";
import { sendAllProps } from "../../types/api";
import { ChatMessageType } from "../../types/chat";
import { FlowType } from "../../types/flow";
@ -8,7 +6,7 @@ import { classNames } from "../../utils/utils";
import ChatInput from "./chatInput";
import ChatMessage from "./chatMessage";
import _ from "lodash";
import _, { cloneDeep } from "lodash";
import AccordionComponent from "../../components/AccordionComponent";
import IconComponent from "../../components/genericIconComponent";
import ToggleShadComponent from "../../components/toggleShadComponent";
@ -24,9 +22,10 @@ import {
import { Textarea } from "../../components/ui/textarea";
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { getBuildStatus } from "../../controllers/API";
import { FlowsState } from "../../types/tabs";
import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
import { FlowState } from "../../types/tabs";
import { validateNodes } from "../../utils/reactflowUtils";
export default function FormModal({
@ -38,15 +37,17 @@ export default function FormModal({
setOpen: (open: boolean) => void;
flow: FlowType;
}): JSX.Element {
const { tabsState, setTabsState } = useContext(FlowsContext);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const flowState = useFlowStore((state) => state.flowState);
const setFlowState = useFlowStore((state) => state.setFlowState);
const [chatValue, setChatValue] = useState(() => {
try {
const { formKeysData } = tabsState[flow.id];
if (!formKeysData) {
throw new Error("formKeysData is undefined");
if (!flowState) {
throw new Error("flowState is undefined");
}
const inputKeys = formKeysData.input_keys;
const handleKeys = formKeysData.handle_keys;
const inputKeys = flowState.input_keys;
const handleKeys = flowState.handle_keys;
const keyToUse = Object.keys(inputKeys!).find(
(key) => !handleKeys?.some((j) => j === key) && inputKeys![key] === ""
@ -61,24 +62,20 @@ export default function FormModal({
});
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
const template = useRef(tabsState[flow.id].formKeysData.template);
const { reactFlowInstance } = useContext(typesContext);
const template = useRef(flowState?.template ?? undefined);
const { accessToken } = useContext(AuthContext);
const { setErrorData } = useContext(alertContext);
const setErrorData = useAlertStore((state) => state.setErrorData);
const ws = useRef<WebSocket | null>(null);
const [lockChat, setLockChat] = useState(false);
const isOpen = useRef(open);
const messagesRef = useRef<HTMLDivElement | null>(null);
const id = useRef(flow.id);
const tabsStateFlowId = tabsState[flow.id];
const tabsStateFlowIdFormKeysData = tabsStateFlowId.formKeysData;
const [chatKey, setChatKey] = useState(() => {
if (tabsState[flow.id]?.formKeysData?.input_keys) {
return Object.keys(tabsState[flow.id].formKeysData.input_keys!).find(
if (flowState?.input_keys) {
return Object.keys(flowState.input_keys!).find(
(key) =>
!tabsState[flow.id].formKeysData.handle_keys!.some(
(j) => j === key
) && tabsState[flow.id].formKeysData.input_keys![key] === ""
!flowState.handle_keys!.some((j) => j === key) &&
flowState.input_keys![key] === ""
);
}
// TODO: return a sensible default
@ -93,9 +90,6 @@ export default function FormModal({
useEffect(() => {
isOpen.current = open;
}, [open]);
useEffect(() => {
id.current = flow.id;
}, [flow.id, tabsStateFlowId, tabsStateFlowIdFormKeysData]);
var isStream = false;
@ -296,7 +290,7 @@ export default function FormModal({
function connectWS(): void {
try {
const urlWs = getWebSocketUrl(
id.current,
flow.id,
process.env.NODE_ENV === "development"
);
const newWs = new WebSocket(urlWs);
@ -384,13 +378,10 @@ export default function FormModal({
}, [open]);
function sendMessage(): void {
let nodeValidationErrors = validateNodes(
reactFlowInstance!.getNodes(),
reactFlowInstance!.getEdges()
);
let nodeValidationErrors = validateNodes(nodes, edges);
if (nodeValidationErrors.length === 0) {
setLockChat(true);
let inputs = tabsState[id.current].formKeysData.input_keys;
let inputs = flowState?.input_keys;
setChatValue("");
const message = inputs;
addChatHistory(message!, true, chatKey!, template.current);
@ -402,13 +393,13 @@ export default function FormModal({
description: flow.description,
chatKey: chatKey!,
});
//@ts-ignore
setTabsState((old: FlowsState) => {
if (!chatKey) return old;
let newTabsState = _.cloneDeep(old);
newTabsState[id.current].formKeysData.input_keys![chatKey] = "";
return newTabsState;
});
if (flowState && chatKey) {
setFlowState((old: FlowState | undefined) => {
let newFlowState = cloneDeep(old!);
newFlowState.input_keys![chatKey] = "";
return newFlowState;
});
}
} else {
setErrorData({
title: "Oops! Looks like you missed some required information:",
@ -418,7 +409,7 @@ export default function FormModal({
}
function clearChat(): void {
setChatHistory([]);
template.current = tabsState[id.current].formKeysData.template;
template.current = flowState?.template;
ws.current?.send(JSON.stringify({ clear_history: true }));
if (lockChat) setLockChat(false);
}
@ -426,7 +417,7 @@ export default function FormModal({
function handleOnCheckedChange(checked: boolean, i: string) {
if (checked === true) {
setChatKey(i);
setChatValue(tabsState[flow.id].formKeysData.input_keys![i]);
setChatValue(flowState?.input_keys![i] ?? "");
} else {
setChatKey(null!);
setChatValue("");
@ -435,7 +426,7 @@ export default function FormModal({
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger hidden></DialogTrigger>
{tabsState[flow.id].formKeysData && (
{flowState && flowState && (
<DialogContent className="min-w-[80vw]">
<DialogHeader>
<DialogTitle className="flex items-center">
@ -471,10 +462,8 @@ export default function FormModal({
</div>
</div>
{tabsState[id.current]?.formKeysData?.input_keys
? Object.keys(
tabsState[id.current].formKeysData.input_keys!
).map((key, index) => (
{flowState?.input_keys
? Object.keys(flowState?.input_keys!).map((key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
@ -495,9 +484,7 @@ export default function FormModal({
handleOnCheckedChange(value, key)
}
size="small"
disabled={tabsState[
id.current
].formKeysData.handle_keys!.some(
disabled={flowState.handle_keys!.some(
(t) => t === key
)}
/>
@ -508,30 +495,23 @@ export default function FormModal({
keyValue={key}
>
<div className="file-component-tab-column">
{tabsState[id.current].formKeysData.handle_keys!.some(
(t) => t === key
) && (
{flowState?.handle_keys!.some((t) => t === key) && (
<div className="font-normal text-muted-foreground ">
Source: Component
</div>
)}
<Textarea
className="custom-scroll"
value={
tabsState[id.current].formKeysData.input_keys![
key
]
}
value={flowState?.input_keys![key]}
onChange={(e) => {
//@ts-ignore
setTabsState((old: FlowsState) => {
let newTabsState = _.cloneDeep(old);
newTabsState[
id.current
].formKeysData.input_keys![key] =
e.target.value;
return newTabsState;
});
if (flowState) {
setFlowState((old: FlowState | undefined) => {
let newFlowState = cloneDeep(old!);
newFlowState.input_keys![key] =
e.target.value;
return newFlowState;
});
}
}}
disabled={chatKey === key}
placeholder="Enter text..."
@ -541,37 +521,35 @@ export default function FormModal({
</div>
))
: null}
{tabsState[id.current].formKeysData.memory_keys!.map(
(key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{key}
</Badge>
<div className="-mb-1">
<ToggleShadComponent
enabled={chatKey === key}
setEnabled={() => {}}
size="small"
disabled={true}
/>
</div>
</div>
}
key={index}
keyValue={key}
>
<div className="file-component-tab-column">
<div className="font-normal text-muted-foreground ">
Source: Memory
{flowState?.memory_keys!.map((key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{key}
</Badge>
<div className="-mb-1">
<ToggleShadComponent
enabled={chatKey === key}
setEnabled={() => {}}
size="small"
disabled={true}
/>
</div>
</div>
</AccordionComponent>
</div>
)
)}
}
key={index}
keyValue={key}
>
<div className="file-component-tab-column">
<div className="font-normal text-muted-foreground ">
Source: Memory
</div>
</div>
</AccordionComponent>
</div>
))}
</div>
<div className="eraser-column-arrangement">
<div className="eraser-size">
@ -635,14 +613,13 @@ export default function FormModal({
sendMessage={sendMessage}
setChatValue={(value) => {
setChatValue(value);
//@ts-ignore
setTabsState((old: FlowsState) => {
let newTabsState = _.cloneDeep(old);
newTabsState[id.current].formKeysData.input_keys![
chatKey!
] = value;
return newTabsState;
});
if (flowState && chatKey) {
setFlowState((old: FlowState | undefined) => {
let newFlowState = cloneDeep(old!);
newFlowState.input_keys![chatKey] = value;
return newFlowState;
});
}
}}
inputRef={ref}
/>

View file

@ -1,4 +1,4 @@
import { useContext, useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import SanitizedHTMLWrapper from "../../components/SanitizedHTMLWrapper";
import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
@ -13,8 +13,8 @@ import {
regexHighlight,
} from "../../constants/constants";
import { TypeModal } from "../../constants/enums";
import { alertContext } from "../../contexts/alertContext";
import { postValidatePrompt } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import { genericModalPropsType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils";
import { classNames, varHighlightHTML } from "../../utils/utils";
@ -40,9 +40,10 @@ export default function GenericModal({
const [inputValue, setInputValue] = useState(value);
const [isEdit, setIsEdit] = useState(true);
const [wordsHighlight, setWordsHighlight] = useState<string[]>([]);
const { setErrorData, setSuccessData, setNoticeData, setModalContextOpen } =
useContext(alertContext);
const ref = useRef();
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setNoticeData = useAlertStore((state) => state.setNoticeData);
const textRef = useRef<HTMLTextAreaElement>(null);
const divRef = useRef(null);
const divRefPrompt = useRef(null);
@ -125,7 +126,8 @@ export default function GenericModal({
if (
JSON.stringify(apiReturn.data?.frontend_node) !== JSON.stringify({})
) {
setNodeClass!(apiReturn.data?.frontend_node, inputValue);
if (setNodeClass)
setNodeClass(apiReturn.data?.frontend_node, inputValue);
setModalOpen(closeModal);
setIsEdit(false);
}
@ -155,10 +157,6 @@ export default function GenericModal({
});
}
useEffect(() => {
setModalContextOpen(modalOpen);
}, [modalOpen]);
return (
<BaseModal
onChangeOpenModal={(open) => {}}
@ -228,8 +226,7 @@ export default function GenericModal({
/>
) : type !== TypeModal.PROMPT ? (
<Textarea
//@ts-ignore
ref={ref}
ref={textRef}
className="form-input h-full w-full rounded-lg focus-visible:ring-1"
value={inputValue}
onChange={(event) => {

View file

@ -1,20 +1,20 @@
import { Loader2 } from "lucide-react";
import { ReactNode, useContext, useEffect, useMemo, useState } from "react";
import { ReactNode, useEffect, useMemo, useState } from "react";
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
import IconComponent from "../../components/genericIconComponent";
import { TagsSelector } from "../../components/tagsSelectorComponent";
import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { StoreContext } from "../../contexts/storeContext";
import { typesContext } from "../../contexts/typesContext";
import {
getStoreComponents,
getStoreTags,
saveFlowStore,
updateFlowStore,
} from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore";
import { FlowType } from "../../types/flow";
import {
downloadNode,
@ -40,10 +40,12 @@ export default function ShareModal({
setOpen?: (open: boolean) => void;
disabled?: boolean;
}): JSX.Element {
const { version, addFlow, downloadFlow } = useContext(FlowsContext);
const { hasApiKey, hasStore } = useContext(StoreContext);
const { setSuccessData, setErrorData } = useContext(alertContext);
const { reactFlowInstance } = useContext(typesContext);
const version = useDarkStore((state) => state.version);
const hasStore = useStoreStore((state) => state.hasStore);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const [internalOpen, internalSetOpen] = useState(children ? false : true);
const [openConfirmationModal, setOpenConfirmationModal] = useState(false);
const nameComponent = is_component ? "component" : "flow";
@ -55,7 +57,7 @@ export default function ShareModal({
const [unavaliableNames, setUnavaliableNames] = useState<
{ id: string; name: string }[]
>([]);
const { saveFlow, flows, tabId } = useContext(FlowsContext);
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
const [loadingNames, setLoadingNames] = useState(false);
@ -180,9 +182,6 @@ export default function ShareModal({
Warning: This action cannot be undone.
</span>
</ConfirmationModal.Content>
<ConfirmationModal.Trigger>
<></>
</ConfirmationModal.Trigger>
</ConfirmationModal>
</>
);

View file

@ -3,9 +3,9 @@ import { useNavigate } from "react-router-dom";
import { Button } from "../../../components/ui/button";
import { Input } from "../../../components/ui/input";
import { CONTROL_LOGIN_STATE } from "../../../constants/constants";
import { alertContext } from "../../../contexts/alertContext";
import { AuthContext } from "../../../contexts/authContext";
import { getLoggedUser, onLogin } from "../../../controllers/API";
import { onLogin } from "../../../controllers/API";
import useAlertStore from "../../../stores/alertStore";
import { LoginType } from "../../../types/api";
import {
inputHandlerEventType,
@ -17,11 +17,10 @@ export default function LoginAdminPage() {
const [inputState, setInputState] =
useState<loginInputStateType>(CONTROL_LOGIN_STATE);
const { login, getAuthentication, setUserData } = useContext(AuthContext);
const { login, isAuthenticated, setUserData } = useContext(AuthContext);
const { password, username } = inputState;
const { setErrorData } = useContext(alertContext);
const setErrorData = useAlertStore((state) => state.setErrorData);
function handleInput({
target: { name, value },
}: inputHandlerEventType): void {
@ -35,8 +34,7 @@ export default function LoginAdminPage() {
};
onLogin(user)
.then((user) => {
login(user.access_token, user.refresh_token);
getUser();
login(user.access_token);
navigate("/admin/");
})
.catch((error) => {
@ -47,20 +45,6 @@ export default function LoginAdminPage() {
});
}
function getUser() {
if (getAuthentication()) {
setTimeout(() => {
getLoggedUser()
.then((user) => {
setUserData(user);
})
.catch((error) => {
console.log("login admin page", error);
});
}, 1000);
}
}
return (
<div className="flex h-full w-full flex-col items-center justify-center bg-muted">
<div className="flex w-72 flex-col items-center justify-center gap-2">

View file

@ -6,7 +6,7 @@ import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import LoadingComponent from "../../components/loadingComponent";
import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox";
import { CheckBoxDiv } from "../../components/ui/checkbox";
import { Input } from "../../components/ui/input";
import {
Table,
@ -20,9 +20,7 @@ import {
ADMIN_HEADER_DESCRIPTION,
ADMIN_HEADER_TITLE,
} from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import {
addUser,
deleteUser,
@ -31,6 +29,8 @@ import {
} from "../../controllers/API";
import ConfirmationModal from "../../modals/ConfirmationModal";
import UserManagementModal from "../../modals/UserManagementModal";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { Users } from "../../types/api";
import { UserInputType } from "../../types/components";
@ -40,15 +40,17 @@ export default function AdminPage() {
const [size, setPageSize] = useState(10);
const [index, setPageIndex] = useState(1);
const [loadingUsers, setLoadingUsers] = useState(true);
const { setErrorData, setSuccessData } = useContext(alertContext);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const { userData } = useContext(AuthContext);
const [totalRowsCount, setTotalRowsCount] = useState(0);
const { setTabId } = useContext(FlowsContext);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
);
// set null id
useEffect(() => {
setTabId("");
setCurrentFlowId("");
}, []);
const userList = useRef([]);
@ -308,7 +310,7 @@ export default function AdminPage() {
</TableCell>
<TableCell className="relative left-1 truncate py-2 text-align-last-left">
<ConfirmationModal
asChild
size="x-small"
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
@ -333,17 +335,14 @@ export default function AdminPage() {
</ConfirmationModal.Content>
<ConfirmationModal.Trigger>
<div className="flex w-fit">
<Checkbox
id="is_active"
checked={user.is_active}
/>
<CheckBoxDiv checked={user.is_active} />
</div>
</ConfirmationModal.Trigger>
</ConfirmationModal>
</TableCell>
<TableCell className="relative left-1 truncate py-2 text-align-last-left">
<ConfirmationModal
asChild
size="x-small"
title="Edit"
titleHeader={`${user.username}`}
modalContentTitle="Attention!"
@ -368,10 +367,7 @@ export default function AdminPage() {
</ConfirmationModal.Content>
<ConfirmationModal.Trigger>
<div className="flex w-fit">
<Checkbox
id="is_superuser"
checked={user.is_superuser}
/>
<CheckBoxDiv checked={user.is_superuser} />
</div>
</ConfirmationModal.Trigger>
</ConfirmationModal>
@ -413,6 +409,7 @@ export default function AdminPage() {
</UserManagementModal>
<ConfirmationModal
size="x-small"
title="Delete"
titleHeader="Delete User"
modalContentTitle="Attention!"
@ -432,12 +429,10 @@ export default function AdminPage() {
</span>
</ConfirmationModal.Content>
<ConfirmationModal.Trigger>
<ShadTooltip content="Delete" side="top">
<IconComponent
name="Trash2"
className="ml-2 h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
<IconComponent
name="Trash2"
className="ml-2 h-4 w-4 cursor-pointer"
/>
</ConfirmationModal.Trigger>
</ConfirmationModal>
</div>

View file

@ -10,7 +10,6 @@ import {
TableHeader,
TableRow,
} from "../../components/ui/table";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { deleteApiKey, getApiKey } from "../../controllers/API";
import ConfirmationModal from "../../modals/ConfirmationModal";
@ -25,11 +24,13 @@ import {
LAST_USED_SPAN_1,
LAST_USED_SPAN_2,
} from "../../constants/constants";
import useAlertStore from "../../stores/alertStore";
import { ApiKey } from "../../types/components";
export default function ApiKeysPage() {
const [loadingKeys, setLoadingKeys] = useState(true);
const { setErrorData, setSuccessData } = useContext(alertContext);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const { userData } = useContext(AuthContext);
const [userId, setUserId] = useState("");
const keysList = useRef([]);

View file

@ -1,47 +1,32 @@
import _ from "lodash";
import {
MouseEvent,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";
import { MouseEvent, useCallback, useEffect, useRef, useState } from "react";
import ReactFlow, {
Background,
Connection,
Controls,
Edge,
EdgeChange,
NodeChange,
NodeDragHandler,
OnMove,
OnSelectionChangeParams,
SelectionDragHandler,
addEdge,
updateEdge,
useEdgesState,
useNodesState,
useReactFlow,
} from "reactflow";
import GenericNode from "../../../../CustomNodes/GenericNode";
import Chat from "../../../../components/chatComponent";
import Loading from "../../../../components/ui/loading";
import { alertContext } from "../../../../contexts/alertContext";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { locationContext } from "../../../../contexts/locationContext";
import { typesContext } from "../../../../contexts/typesContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useTypesStore } from "../../../../stores/typesStore";
import { APIClassType } from "../../../../types/api";
import { FlowType, NodeType, targetHandleType } from "../../../../types/flow";
import { FlowsState } from "../../../../types/tabs";
import { FlowType, NodeType } from "../../../../types/flow";
import {
generateFlow,
generateNodeFromFlow,
getNodeId,
isValidConnection,
scapeJSONParse,
validateSelection,
} from "../../../../utils/reactflowUtils";
import { cn, getRandomName, isWrappedWithClass } from "../../../../utils/utils";
import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
import ConnectionLineComponent from "../ConnectionLineComponent";
import SelectionMenu from "../SelectionMenuComponent";
import ExtraSidebar from "../extraSidebarComponent";
@ -57,45 +42,60 @@ export default function Page({
flow: FlowType;
view?: boolean;
}): JSX.Element {
let {
updateFlow,
uploadFlow,
getNodeId,
paste,
lastCopiedSelection,
setLastCopiedSelection,
tabsState,
saveFlow,
setTabsState,
tabId,
saveCurrentFlow,
} = useContext(FlowsContext);
const {
types,
reactFlowInstance,
setReactFlowInstance,
templates,
setFilterEdge,
deleteNode,
deleteEdge,
} = useContext(typesContext);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const autoSaveCurrentFlow = useFlowsManagerStore(
(state) => state.autoSaveCurrentFlow
);
const types = useTypesStore((state) => state.types);
const templates = useTypesStore((state) => state.templates);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const { takeSnapshot } = useContext(undoRedoContext);
const { nodesOnFlow, setNodesOnFlow } = useContext(FlowsContext);
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
const setReactFlowInstance = useFlowStore(
(state) => state.setReactFlowInstance
);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const onNodesChange = useFlowStore((state) => state.onNodesChange);
const onEdgesChange = useFlowStore((state) => state.onEdgesChange);
const setNodes = useFlowStore((state) => state.setNodes);
const setEdges = useFlowStore((state) => state.setEdges);
const cleanFlow = useFlowStore((state) => state.cleanFlow);
const deleteNode = useFlowStore((state) => state.deleteNode);
const deleteEdge = useFlowStore((state) => state.deleteEdge);
const undo = useFlowsManagerStore((state) => state.undo);
const redo = useFlowsManagerStore((state) => state.redo);
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const paste = useFlowStore((state) => state.paste);
const resetFlow = useFlowStore((state) => state.resetFlow);
const lastCopiedSelection = useFlowStore(
(state) => state.lastCopiedSelection
);
const setLastCopiedSelection = useFlowStore(
(state) => state.setLastCopiedSelection
);
const onConnect = useFlowStore((state) => state.onConnect);
const position = useRef({ x: 0, y: 0 });
const [lastSelection, setLastSelection] =
useState<OnSelectionChangeParams | null>(null);
const saveCurrentFlowTimeout = () => {
setTimeout(() => {
saveCurrentFlow();
}, 500); // need to do this because ReactFlow is not asynchronous.
};
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (!isWrappedWithClass(event, "noundo")) {
if (
(event.key === "y" || (event.key === "z" && event.shiftKey)) &&
(event.ctrlKey || event.metaKey)
) {
event.preventDefault(); // prevent the default action
redo();
} else if (event.key === "z" && (event.ctrlKey || event.metaKey)) {
event.preventDefault();
undo();
}
}
if (
!isWrappedWithClass(event, "nocopy") &&
window.getSelection()?.toString().length === 0
@ -107,8 +107,7 @@ export default function Page({
) {
event.preventDefault();
setLastCopiedSelection(_.cloneDeep(lastSelection));
}
if (
} else if (
(event.ctrlKey || event.metaKey) &&
event.key === "v" &&
lastCopiedSelection
@ -119,8 +118,7 @@ export default function Page({
x: position.current.x,
y: position.current.y,
});
}
if (
} else if (
(event.ctrlKey || event.metaKey) &&
event.key === "g" &&
lastSelection
@ -137,7 +135,6 @@ export default function Page({
takeSnapshot();
deleteNode(lastSelection.nodes.map((node) => node.id));
deleteEdge(lastSelection.edges.map((edge) => edge.id));
saveCurrentFlowTimeout();
}
}
};
@ -153,148 +150,38 @@ export default function Page({
document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("mousemove", handleMouseMove);
};
}, [
lastCopiedSelection,
lastSelection,
takeSnapshot,
saveCurrentFlowTimeout,
]);
}, [lastCopiedSelection, lastSelection, takeSnapshot]);
const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
const { setErrorData } = useContext(alertContext);
const [nodes, setNodes, onNodesChange] = useNodesState(
flow.data?.nodes ?? []
);
const setErrorData = useAlertStore((state) => state.setErrorData);
const [edges, setEdges, onEdgesChange] = useEdgesState(
flow.data?.edges ?? []
);
const { setViewport } = useReactFlow();
const edgeUpdateSuccessful = useRef(true);
const [loading, setLoading] = useState(true);
const timeoutRef = useRef<NodeJS.Timeout>();
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
useEffect(() => {
setLoading(true);
setNodes(flow?.data?.nodes ?? []);
setEdges(flow?.data?.edges ?? []);
setViewport(flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 });
// Clear the previous timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// Create a new timeout
timeoutRef.current = setTimeout(() => {
setLoading(false);
}, 300);
// Clear the timeout when the component is unmounted
return () => {
clearTimeout(timeoutRef.current);
};
}, [flow, reactFlowInstance]);
useEffect(() => {
const interval = setInterval(() => {
saveFlow(flow, true);
}, 30000);
return () => {
clearInterval(interval);
};
}, [flow, flow.data]);
const onEdgesChangeMod = useCallback(
(change: EdgeChange[]) => {
onEdgesChange(change);
//@ts-ignore
setTabsState((prev: FlowsState) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
if (reactFlowInstance) {
resetFlow({
nodes: flow?.data?.nodes ?? [],
edges: flow?.data?.edges ?? [],
viewport: flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 },
});
saveCurrentFlowTimeout();
},
[onEdgesChange, setNodes, setTabsState, saveCurrentFlowTimeout, tabId]
);
}
}, [currentFlowId, reactFlowInstance]);
const onNodesChangeMod = useCallback(
(change: NodeChange[]) => {
const changeString = JSON.stringify(change);
if (changeString !== nodesOnFlow) {
onNodesChange(change);
updateNodeFlow(changeString);
//@ts-ignore
setTabsState((prev: FlowsState) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
saveCurrentFlowTimeout();
}
},
[onNodesChange, setTabsState, tabId, updateNodeFlow, saveCurrentFlowTimeout]
);
useEffect(() => {
return () => {
cleanFlow();
};
}, []);
function updateNodeFlow(changeString: string) {
setNodesOnFlow(changeString);
}
const onConnect = useCallback(
const onConnectMod = useCallback(
(params: Connection) => {
takeSnapshot();
setEdges((eds) =>
addEdge(
{
...params,
data: {
targetHandle: scapeJSONParse(params.targetHandle!),
sourceHandle: scapeJSONParse(params.sourceHandle!),
},
style: { stroke: "#555" },
className:
((scapeJSONParse(params.targetHandle!) as targetHandleType)
.type === "Text"
? "stroke-foreground "
: "stroke-foreground ") + " stroke-connection",
animated:
(scapeJSONParse(params.targetHandle!) as targetHandleType)
.type === "Text",
},
eds
)
);
setNodes((node) => {
let newX = _.cloneDeep(node);
return newX;
});
//@ts-ignore
setTabsState((prev: FlowsState) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
saveCurrentFlowTimeout();
onConnect(params);
},
[setEdges, setNodes, takeSnapshot, addEdge]
[takeSnapshot, onConnect]
);
const onNodeDragStart: NodeDragHandler = useCallback(() => {
@ -303,6 +190,16 @@ export default function Page({
// 👉 you can place your event handlers here
}, [takeSnapshot]);
const onNodeDragStop: NodeDragHandler = useCallback(() => {
autoSaveCurrentFlow(nodes, edges, reactFlowInstance?.getViewport()!);
// 👉 you can place your event handlers here
}, [takeSnapshot, autoSaveCurrentFlow, nodes, edges, reactFlowInstance]);
const onMoveEnd: OnMove = useCallback(() => {
// 👇 make moving the canvas undoable
autoSaveCurrentFlow(nodes, edges, reactFlowInstance?.getViewport()!);
}, [takeSnapshot, autoSaveCurrentFlow, nodes, edges, reactFlowInstance]);
const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
// 👇 make dragging a selection undoable
takeSnapshot();
@ -323,52 +220,26 @@ export default function Page({
if (event.dataTransfer.types.some((types) => types === "nodedata")) {
takeSnapshot();
// Get the current bounds of the ReactFlow wrapper element
const reactflowBounds =
reactFlowWrapper.current?.getBoundingClientRect();
// Extract the data from the drag event and parse it as a JSON object
let data: { type: string; node?: APIClassType } = JSON.parse(
const data: { type: string; node?: APIClassType } = JSON.parse(
event.dataTransfer.getData("nodedata")
);
// Calculate the position where the node should be created
const position = reactFlowInstance!.screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
const newId = getNodeId(data.type);
// Generate a unique node ID
let { type } = data;
let newId = getNodeId(type);
let newNode: NodeType;
if (data.type !== "groupNode") {
// Create a new node object
newNode = {
const newNode: NodeType = {
id: newId,
type: "genericNode",
position: { x: 0, y: 0 },
data: {
...data,
id: newId,
type: "genericNode",
position,
data: {
...data,
id: newId,
},
};
} else {
// Create a new node object
newNode = {
id: newId,
type: "genericNode",
position,
data: {
...data,
id: newId,
},
};
// Add the new node to the list of nodes in state
}
setNodes((nds) => nds.concat(newNode));
},
};
paste(
{ nodes: [newNode], edges: [] },
{ x: event.clientX, y: event.clientY }
);
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
takeSnapshot();
if (event.dataTransfer.files.item(0)!.type === "application/json") {
@ -396,32 +267,21 @@ export default function Page({
}
},
// Specify dependencies for useCallback
[getNodeId, reactFlowInstance, setNodes, takeSnapshot]
[getNodeId, setNodes, takeSnapshot, paste]
);
useEffect(() => {
setExtraComponent(<ExtraSidebar />);
setExtraNavigation({ title: "Components" });
return () => {
if (tabsState && tabsState[flow.id]?.isPending) {
saveFlow(flow);
}
};
}, []);
const onEdgeUpdateStart = useCallback(() => {
edgeUpdateSuccessful.current = false;
}, []);
const onEdgeUpdate = useCallback(
(oldEdge: Edge, newConnection: Connection) => {
if (isValidConnection(newConnection, reactFlowInstance!)) {
if (isValidConnection(newConnection, nodes, edges)) {
edgeUpdateSuccessful.current = true;
setEdges((els) => updateEdge(oldEdge, newConnection, els));
}
},
[reactFlowInstance, setEdges]
[setEdges]
);
const onEdgeUpdateEnd = useCallback((_, edge: Edge): void => {
@ -461,20 +321,6 @@ export default function Page({
setFilterEdge([]);
}, []);
const onMove = useCallback(() => {
saveCurrentFlowTimeout();
//@ts-ignore
setTabsState((prev: FlowsState) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
}, [setTabsState, saveCurrentFlowTimeout]);
return (
<div className="flex h-full overflow-hidden">
{!view && <ExtraSidebar />}
@ -486,21 +332,12 @@ export default function Page({
{Object.keys(templates).length > 0 &&
Object.keys(types).length > 0 ? (
<div id="react-flow-id" className="h-full w-full">
<div
className={cn(
"relative flex h-full w-full items-center justify-center bg-background",
!loading ? "hidden" : ""
)}
>
<Loading />
</div>
<ReactFlow
nodes={nodes}
onMove={onMove}
edges={edges}
onNodesChange={onNodesChangeMod}
onEdgesChange={onEdgesChangeMod}
onConnect={onConnect}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnectMod}
disableKeyboardA11y={true}
onInit={setReactFlowInstance}
nodeTypes={nodeTypes}
@ -508,11 +345,13 @@ export default function Page({
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
onNodeDragStart={onNodeDragStart}
onNodeDragStop={onNodeDragStop}
onSelectionDragStart={onSelectionDragStart}
onSelectionEnd={onSelectionEnd}
onSelectionStart={onSelectionStart}
connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver}
onMoveEnd={onMoveEnd}
onDrop={onDrop}
onSelectionChange={onSelectionChange}
deleteKeyCode={[]}
@ -542,7 +381,8 @@ export default function Page({
) {
const { newFlow } = generateFlow(
lastSelection!,
reactFlowInstance!,
nodes,
edges,
getRandomName()
);
const newGroupNode = generateNodeFromFlow(
@ -578,9 +418,7 @@ export default function Page({
}}
/>
</ReactFlow>
{!view && (
<Chat flow={flow} reactFlowInstance={reactFlowInstance!} />
)}
{!view && <Chat flow={flow} />}
</div>
) : (
<></>

View file

@ -1,16 +1,17 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
import { Input } from "../../../../components/ui/input";
import { Separator } from "../../../../components/ui/separator";
import { alertContext } from "../../../../contexts/alertContext";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { StoreContext } from "../../../../contexts/storeContext";
import { typesContext } from "../../../../contexts/typesContext";
import ApiModal from "../../../../modals/ApiModal";
import ExportModal from "../../../../modals/exportModal";
import ShareModal from "../../../../modals/shareModal";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useStoreStore } from "../../../../stores/storeStore";
import { useTypesStore } from "../../../../stores/typesStore";
import { APIClassType, APIObjectType } from "../../../../types/api";
import {
nodeColors,
@ -26,15 +27,20 @@ import DisclosureComponent from "../DisclosureComponent";
import SidebarDraggableComponent from "./sideBarDraggableComponent";
export default function ExtraSidebar(): JSX.Element {
const { data, templates, getFilterEdge, setFilterEdge, reactFlowInstance } =
useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, version } =
useContext(FlowsContext);
const { hasApiKey, validApiKey, hasStore } = useContext(StoreContext);
const { setErrorData } = useContext(alertContext);
const data = useTypesStore((state) => state.data);
const templates = useTypesStore((state) => state.templates);
const getFilterEdge = useFlowStore((state) => state.getFilterEdge);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const hasStore = useStoreStore((state) => state.hasStore);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
const validApiKey = useStoreStore((state) => state.validApiKey);
const isBuilt = useFlowStore((state) => state.isBuilt);
const setErrorData = useAlertStore((state) => state.setErrorData);
const [dataFilter, setFilterData] = useState(data);
const [search, setSearch] = useState("");
const isPending = tabsState[tabId]?.isPending;
function onDragStart(
event: React.DragEvent<any>,
data: { type: string; node?: APIClassType }
@ -72,7 +78,7 @@ export default function ExtraSidebar(): JSX.Element {
return ret;
});
}
const flow = flows.find((flow) => flow.id === tabId);
useEffect(() => {
// show components with error on load
let errors: string[] = [];
@ -187,7 +193,7 @@ export default function ExtraSidebar(): JSX.Element {
() => (
<ShareModal
is_component={false}
component={flow!}
component={currentFlow!}
disabled={!hasApiKey || !validApiKey || !hasStore}
>
<button
@ -212,17 +218,15 @@ export default function ExtraSidebar(): JSX.Element {
</button>
</ShareModal>
),
[hasApiKey, validApiKey, flow, hasStore]
[hasApiKey, validApiKey, currentFlow, hasStore]
);
const ExportMemo = useMemo(
() => (
<ExportModal>
<ShadTooltip content="Export" side="top">
<button className={classNames("extra-side-bar-buttons")}>
<IconComponent name="FileDown" className="side-bar-button-size" />
</button>
</ShadTooltip>
<button className={classNames("extra-side-bar-buttons")}>
<IconComponent name="FileDown" className="side-bar-button-size" />
</button>
</ExportModal>
),
[]
@ -263,11 +267,19 @@ export default function ExtraSidebar(): JSX.Element {
</button>
</ShadTooltip>
</div>
{(!hasApiKey || !validApiKey) && ExportMemo}
{(!hasApiKey || !validApiKey) && (
<ShadTooltip
content="Export"
side="top"
styleClasses="cursor-default"
>
<div className="side-bar-button">{ExportMemo}</div>
</ShadTooltip>
)}
<ShadTooltip content={"Code"} side="top">
<div className="side-bar-button">
{flow && flow.data && (
<ApiModal flow={flow}>
{currentFlow && currentFlow.data && (
<ApiModal flow={currentFlow}>
<button
className={"w-full " + (!isBuilt ? "button-disable" : "")}
>
@ -285,34 +297,6 @@ export default function ExtraSidebar(): JSX.Element {
)}
</div>
</ShadTooltip>
<div className="side-bar-button" data-testid="save-button">
{flow && flow.data && (
<ShadTooltip content="Save" side="top">
<button
disabled={flow?.data?.nodes.length === 0}
className={
"extra-side-bar-buttons " +
(isPending && flow!.data!.nodes?.length > 0
? ""
: "button-disable")
}
onClick={(event) => {
saveFlow(flow!);
}}
>
<IconComponent
name="Save"
className={
"side-bar-button-size" +
(isPending && flow!.data!.nodes?.length > 0
? " "
: " extra-side-bar-save-disable")
}
/>
</button>
</ShadTooltip>
)}
</div>
</div>
<Separator />
<div className="side-bar-search-div-placement">

View file

@ -1,4 +1,4 @@
import { DragEventHandler, useContext, useRef, useState } from "react";
import { DragEventHandler, forwardRef, useRef, useState } from "react";
import IconComponent from "../../../../../components/genericIconComponent";
import {
Select,
@ -6,147 +6,157 @@ import {
SelectItem,
SelectTrigger,
} from "../../../../../components/ui/select-custom";
import { AuthContext } from "../../../../../contexts/authContext";
import { FlowsContext } from "../../../../../contexts/flowsContext";
import { useDarkStore } from "../../../../../stores/darkStore";
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
import { APIClassType } from "../../../../../types/api";
import {
createFlowComponent,
downloadNode,
getNodeId,
} from "../../../../../utils/reactflowUtils";
import { removeCountFromString } from "../../../../../utils/utils";
export default function SidebarDraggableComponent({
sectionName,
display_name,
itemName,
error,
color,
onDragStart,
apiClass,
official,
}: {
sectionName: string;
apiClass: APIClassType;
display_name: string;
itemName: string;
error: boolean;
color: string;
onDragStart: DragEventHandler<HTMLDivElement>;
official: boolean;
}) {
const [open, setOpen] = useState(false);
const { getNodeId, deleteComponent, version } = useContext(FlowsContext);
const { autoLogin, userData } = useContext(AuthContext);
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
const popoverRef = useRef<HTMLDivElement>(null);
export const SidebarDraggableComponent = forwardRef(
(
{
sectionName,
display_name,
itemName,
error,
color,
onDragStart,
apiClass,
official,
}: {
sectionName: string;
apiClass: APIClassType;
display_name: string;
itemName: string;
error: boolean;
color: string;
onDragStart: DragEventHandler<HTMLDivElement>;
official: boolean;
},
ref
) => {
const [open, setOpen] = useState(false);
const deleteComponent = useFlowsManagerStore(
(state) => state.deleteComponent
);
const version = useDarkStore((state) => state.version);
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
const popoverRef = useRef<HTMLDivElement>(null);
const handlePointerDown = (e) => {
if (!open) {
const rect = popoverRef.current?.getBoundingClientRect() ?? {
left: 0,
top: 0,
};
setCursorPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
}
};
const handlePointerDown = (e) => {
if (!open) {
const rect = popoverRef.current?.getBoundingClientRect() ?? {
left: 0,
top: 0,
};
setCursorPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
}
};
function handleSelectChange(value: string) {
switch (value) {
case "share":
break;
case "download":
const type = removeCountFromString(itemName);
downloadNode(
createFlowComponent(
{ id: getNodeId(type), type, node: apiClass },
version
)
);
break;
case "delete":
deleteComponent(display_name);
break;
function handleSelectChange(value: string) {
switch (value) {
case "share":
break;
case "download":
const type = removeCountFromString(itemName);
downloadNode(
createFlowComponent(
{ id: getNodeId(type), type, node: apiClass },
version
)
);
break;
case "delete":
deleteComponent(display_name);
break;
}
}
}
return (
<Select
onValueChange={handleSelectChange}
onOpenChange={(change) => setOpen(change)}
open={open}
key={itemName}
>
<div
onPointerDown={handlePointerDown}
onContextMenuCapture={(e) => {
e.preventDefault();
setOpen(true);
}}
return (
<Select
onValueChange={handleSelectChange}
onOpenChange={(change) => setOpen(change)}
open={open}
key={itemName}
data-tooltip-id={itemName}
>
<div
draggable={!error}
className={
"side-bar-components-border bg-background" +
(error ? " cursor-not-allowed select-none" : "")
}
style={{
borderLeftColor: color,
}}
onDragStart={onDragStart}
onDragEnd={() => {
document.body.removeChild(
document.getElementsByClassName("cursor-grabbing")[0]
);
onPointerDown={handlePointerDown}
onContextMenuCapture={(e) => {
e.preventDefault();
setOpen(true);
}}
key={itemName}
data-tooltip-id={itemName}
>
<div
data-testid={sectionName + display_name}
id={sectionName + display_name}
className="side-bar-components-div-form"
draggable={!error}
className={
"side-bar-components-border bg-background" +
(error ? " cursor-not-allowed select-none" : "")
}
style={{
borderLeftColor: color,
}}
onDragStart={onDragStart}
onDragEnd={() => {
document.body.removeChild(
document.getElementsByClassName("cursor-grabbing")[0]
);
}}
>
<span className="side-bar-components-text">{display_name}</span>
<div ref={popoverRef}>
<IconComponent
name="Menu"
className="side-bar-components-icon "
/>
<SelectTrigger></SelectTrigger>
<SelectContent
position="popper"
side="bottom"
sideOffset={-25}
style={{
position: "absolute",
left: cursorPos.x,
top: cursorPos.y,
}}
>
<SelectItem value={"download"}>
<div className="flex">
<IconComponent
name="Download"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
Download{" "}
</div>{" "}
</SelectItem>
{!official && (
<SelectItem value={"delete"}>
<div
data-testid={sectionName + display_name}
id={sectionName + display_name}
className="side-bar-components-div-form"
>
<span className="side-bar-components-text">{display_name}</span>
<div ref={popoverRef}>
<IconComponent
name="Menu"
className="side-bar-components-icon "
/>
<SelectTrigger></SelectTrigger>
<SelectContent
position="popper"
side="bottom"
sideOffset={-25}
style={{
position: "absolute",
left: cursorPos.x,
top: cursorPos.y,
}}
>
<SelectItem value={"download"}>
<div className="flex">
<IconComponent
name="Trash2"
name="Download"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
Delete{" "}
Download{" "}
</div>{" "}
</SelectItem>
)}
</SelectContent>
{!official && (
<SelectItem value={"delete"}>
<div className="flex">
<IconComponent
name="Trash2"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
Delete{" "}
</div>{" "}
</SelectItem>
)}
</SelectContent>
</div>
</div>
</div>
</div>
</div>
</Select>
);
}
</Select>
);
}
);
export default SidebarDraggableComponent;

View file

@ -1,6 +1,5 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useState } from "react";
import { useReactFlow, useUpdateNodeInternals } from "reactflow";
import { useEffect, useState } from "react";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
import {
@ -9,12 +8,13 @@ import {
SelectItem,
SelectTrigger,
} from "../../../../components/ui/select-custom";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { StoreContext } from "../../../../contexts/storeContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import ConfirmationModal from "../../../../modals/ConfirmationModal";
import EditNodeModal from "../../../../modals/EditNodeModal";
import ShareModal from "../../../../modals/shareModal";
import { useDarkStore } from "../../../../stores/darkStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useStoreStore } from "../../../../stores/storeStore";
import { nodeToolbarPropsType } from "../../../../types/components";
import { FlowType } from "../../../../types/flow";
import {
@ -32,27 +32,27 @@ export default function NodeToolbarComponent({
setShowNode,
numberOfHandles,
showNode,
setIsMinimized,
}: nodeToolbarPropsType): JSX.Element {
const [nodeLength, setNodeLength] = useState(
Object.keys(data.node!.template).filter(
(templateField) =>
templateField.charAt(0) !== "_" &&
data.node?.template[templateField].show &&
(data.node.template[templateField].type === "str" ||
data.node.template[templateField].type === "bool" ||
data.node.template[templateField].type === "float" ||
data.node.template[templateField].type === "code" ||
data.node.template[templateField].type === "prompt" ||
data.node.template[templateField].type === "file" ||
data.node.template[templateField].type === "Any" ||
data.node.template[templateField].type === "int" ||
data.node.template[templateField].type === "dict" ||
data.node.template[templateField].type === "NestedDict")
).length
);
const updateNodeInternals = useUpdateNodeInternals();
const { getNodeId } = useContext(FlowsContext);
const { hasApiKey, validApiKey, hasStore } = useContext(StoreContext);
const nodeLength = Object.keys(data.node!.template).filter(
(templateField) =>
templateField.charAt(0) !== "_" &&
data.node?.template[templateField].show &&
(data.node.template[templateField].type === "str" ||
data.node.template[templateField].type === "bool" ||
data.node.template[templateField].type === "float" ||
data.node.template[templateField].type === "code" ||
data.node.template[templateField].type === "prompt" ||
data.node.template[templateField].type === "file" ||
data.node.template[templateField].type === "Any" ||
data.node.template[templateField].type === "int" ||
data.node.template[templateField].type === "dict" ||
data.node.template[templateField].type === "NestedDict")
).length;
const hasStore = useStoreStore((state) => state.hasStore);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
const validApiKey = useStoreStore((state) => state.validApiKey);
function canMinimize() {
let countHandles: number = 0;
@ -65,9 +65,16 @@ export default function NodeToolbarComponent({
const isMinimal = canMinimize();
const isGroup = data.node?.flow ? true : false;
const { paste, saveComponent, version, flows } = useContext(FlowsContext);
const { takeSnapshot } = useContext(undoRedoContext);
const reactFlowInstance = useReactFlow();
const paste = useFlowStore((state) => state.paste);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setNodes = useFlowStore((state) => state.setNodes);
const setEdges = useFlowStore((state) => state.setEdges);
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
const flows = useFlowsManagerStore((state) => state.flows);
const version = useDarkStore((state) => state.version);
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const [showModalAdvanced, setShowModalAdvanced] = useState(false);
const [showconfirmShare, setShowconfirmShare] = useState(false);
const [selectedValue, setSelectedValue] = useState("");
@ -91,6 +98,10 @@ export default function NodeToolbarComponent({
showconfirmShare,
]);
useEffect(() => {
setIsMinimized(!showNode);
}, [showNode]);
const handleSelectChange = (event) => {
switch (event) {
case "advanced":
@ -99,7 +110,6 @@ export default function NodeToolbarComponent({
case "show":
takeSnapshot();
setShowNode(data.showNode ?? true ? false : true);
updateNodeInternals(data.id);
break;
case "Download":
downloadNode(createFlowComponent(cloneDeep(data), version));
@ -115,7 +125,7 @@ export default function NodeToolbarComponent({
case "ungroup":
takeSnapshot();
updateFlowPosition(position, data.node?.flow!);
expandGroupNode(data, reactFlowInstance, getNodeId);
expandGroupNode(data, nodes, edges, setNodes, setEdges);
break;
case "override":
setShowOverrideModal(true);
@ -152,14 +162,16 @@ export default function NodeToolbarComponent({
event.preventDefault();
paste(
{
nodes: [reactFlowInstance.getNode(data.id)],
nodes: [nodes.find((node) => node.id === data.id)!],
edges: [],
},
{
x: 50,
y: 10,
paneX: reactFlowInstance.getNode(data.id)?.position.x,
paneY: reactFlowInstance.getNode(data.id)?.position.y,
paneX: nodes.find((node) => node.id === data.id)?.position
.x,
paneY: nodes.find((node) => node.id === data.id)?.position
.y,
}
);
}}
@ -287,7 +299,6 @@ export default function NodeToolbarComponent({
</Select>
<ConfirmationModal
asChild
open={showOverrideModal}
title={`Replace`}
cancelText="Create New"
@ -307,9 +318,6 @@ export default function NodeToolbarComponent({
to replace it with the current or create a new one?
</span>
</ConfirmationModal.Content>
<ConfirmationModal.Trigger>
<></>
</ConfirmationModal.Trigger>
</ConfirmationModal>
<EditNodeModal
data={data}

View file

@ -1,27 +1,28 @@
import { useContext, useEffect } from "react";
import { useEffect } from "react";
import { useParams } from "react-router-dom";
import Header from "../../components/headerComponent";
import { FlowsContext } from "../../contexts/flowsContext";
import { useDarkStore } from "../../stores/darkStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import Page from "./components/PageComponent";
export default function FlowPage(): JSX.Element {
const { flows, tabId, setTabId, version } = useContext(FlowsContext);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
);
const version = useDarkStore((state) => state.version);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const { id } = useParams();
// Set flow tab id
useEffect(() => {
setTabId(id!);
setCurrentFlowId(id!);
}, [id]);
return (
<>
<Header />
<div className="flow-page-positioning">
{flows.length > 0 &&
tabId !== "" &&
flows.findIndex((flow) => flow.id === tabId) !== -1 && (
<Page flow={flows.find((flow) => flow.id === tabId)!} />
)}
{currentFlow && <Page flow={currentFlow} />}
<a
target={"_blank"}
href="https://logspace.ai/"

View file

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import PaginatorComponent from "../../../../components/PaginatorComponent";
import CollectionCardComponent from "../../../../components/cardComponent";
@ -6,8 +6,8 @@ import CardsWrapComponent from "../../../../components/cardsWrapComponent";
import IconComponent from "../../../../components/genericIconComponent";
import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent";
import { Button } from "../../../../components/ui/button";
import { alertContext } from "../../../../contexts/alertContext";
import { FlowsContext } from "../../../../contexts/flowsContext";
import useAlertStore from "../../../../stores/alertStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { FlowType } from "../../../../types/flow";
export default function ComponentsComponent({
@ -15,9 +15,13 @@ export default function ComponentsComponent({
}: {
is_component?: boolean;
}) {
const { flows, removeFlow, uploadFlow, addFlow, isLoading } =
useContext(FlowsContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const removeFlow = useFlowsManagerStore((state) => state.removeFlow);
const isLoading = useFlowsManagerStore((state) => state.isLoading);
const flows = useFlowsManagerStore((state) => state.flows);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const [pageSize, setPageSize] = useState(10);
const [pageIndex, setPageIndex] = useState(1);
const [loadingScreen, setLoadingScreen] = useState(true);
@ -48,7 +52,7 @@ export default function ComponentsComponent({
const start = (pageIndex - 1) * pageSize;
const end = start + pageSize;
setData(all.slice(start, end));
}, [flows, pageIndex, pageSize]);
}, [flows, isLoading, pageIndex, pageSize]);
const [data, setData] = useState<FlowType[]>([]);
@ -155,6 +159,9 @@ export default function ComponentsComponent({
variant="outline"
size="sm"
className="whitespace-nowrap "
data-testid={
"edit-flow-button-" + item.id + "-" + idx
}
>
<IconComponent
name="ExternalLink"

View file

@ -1,5 +1,5 @@
import { Group, ToyBrick } from "lucide-react";
import { useContext, useEffect } from "react";
import { useEffect } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import DropdownButton from "../../components/DropdownButtonComponent";
import IconComponent from "../../components/genericIconComponent";
@ -7,12 +7,18 @@ import PageLayout from "../../components/pageLayout";
import SidebarNav from "../../components/sidebarComponent";
import { Button } from "../../components/ui/button";
import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { downloadFlows } from "../../utils/reactflowUtils";
export default function HomePage(): JSX.Element {
const { setTabId, downloadFlows, uploadFlows, addFlow, uploadFlow } =
useContext(FlowsContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
);
const uploadFlows = useFlowsManagerStore((state) => state.uploadFlows);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const location = useLocation();
const pathname = location.pathname;
const is_component = pathname === "/components";
@ -56,7 +62,7 @@ export default function HomePage(): JSX.Element {
// Set a null id
useEffect(() => {
setTabId("");
setCurrentFlowId("");
}, [pathname]);
const navigate = useNavigate();

View file

@ -7,17 +7,19 @@ import Header from "../../components/headerComponent";
import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { resetPassword, updateUser } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import {
inputHandlerEventType,
patchUserInputStateType,
} from "../../types/components";
import { gradients } from "../../utils/styleUtils";
export default function ProfileSettingsPage(): JSX.Element {
const { setTabId } = useContext(FlowsContext);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
);
const [inputState, setInputState] = useState<patchUserInputStateType>(
CONTROL_PATCH_USER_STATE
@ -25,9 +27,10 @@ export default function ProfileSettingsPage(): JSX.Element {
// set null id
useEffect(() => {
setTabId("");
setCurrentFlowId("");
}, []);
const { setErrorData, setSuccessData } = useContext(alertContext);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const { userData, setUserData } = useContext(AuthContext);
const { password, cnfPassword, gradient } = inputState;
@ -97,6 +100,7 @@ export default function ProfileSettingsPage(): JSX.Element {
Password{" "}
</Form.Label>
<InputComponent
id="pasword"
onChange={(value) => {
handleInput({ target: { name: "password", value } });
}}
@ -118,6 +122,7 @@ export default function ProfileSettingsPage(): JSX.Element {
</Form.Label>
<InputComponent
id="cnfPassword"
onChange={(value) => {
handleInput({ target: { name: "cnfPassword", value } });
}}
@ -143,9 +148,9 @@ export default function ProfileSettingsPage(): JSX.Element {
<GradientChooserComponent
value={
gradient == ""
? userData!.profile_image ??
? userData?.profile_image ??
gradients[
parseInt(userData!.id ?? "", 30) % gradients.length
parseInt(userData?.id ?? "", 30) % gradients.length
]
: gradient
}

View file

@ -20,20 +20,34 @@ import {
SelectTrigger,
SelectValue,
} from "../../components/ui/select";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { StoreContext } from "../../contexts/storeContext";
import { getStoreComponents, getStoreTags } from "../../controllers/API";
import {
checkHasApiKey,
getStoreComponents,
getStoreTags,
} from "../../controllers/API";
import StoreApiKeyModal from "../../modals/StoreApiKeyModal";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore";
import { storeComponent } from "../../types/store";
import { cn } from "../../utils/utils";
export default function StorePage(): JSX.Element {
const { validApiKey, setValidApiKey, hasApiKey, loadingApiKey } =
useContext(StoreContext);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
const validApiKey = useStoreStore((state) => state.validApiKey);
const loadingApiKey = useStoreStore((state) => state.loadingApiKey);
const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
const setLoadingApiKey = useStoreStore((state) => state.updateLoadingApiKey);
const setHasApiKey = useStoreStore((state) => state.updateHasApiKey);
const { apiKey } = useContext(AuthContext);
const { setErrorData } = useContext(alertContext);
const { setTabId } = useContext(FlowsContext);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
);
const [loading, setLoading] = useState(true);
const [loadingTags, setLoadingTags] = useState(true);
const { id } = useParams();
@ -154,7 +168,7 @@ export default function StorePage(): JSX.Element {
// Set a null id
useEffect(() => {
setTabId("");
setCurrentFlowId("");
}, []);
function resetPagination() {
@ -162,6 +176,23 @@ export default function StorePage(): JSX.Element {
setPageSize(12);
}
const fetchApiData = async () => {
setLoadingApiKey(true);
try {
const res = await checkHasApiKey();
setHasApiKey(res?.has_api_key ?? false);
setValidApiKey(res?.is_valid ?? false);
setLoadingApiKey(false);
} catch (e) {
setLoadingApiKey(false);
console.log(e);
}
};
useEffect(() => {
fetchApiData();
}, [apiKey]);
return (
<PageLayout
betaIcon

View file

@ -1,24 +1,23 @@
import { useContext, useEffect } from "react";
import { useEffect } from "react";
import { useParams } from "react-router-dom";
import { FlowsContext } from "../../contexts/flowsContext";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import Page from "../FlowPage/components/PageComponent";
export default function ViewPage() {
const { flows, tabId, setTabId } = useContext(FlowsContext);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
);
const { id } = useParams();
// Set flow tab id
useEffect(() => {
setTabId(id!);
setCurrentFlowId(id!);
}, [id]);
return (
<div className="flow-page-positioning">
{flows.length > 0 &&
tabId !== "" &&
flows.findIndex((flow) => flow.id === tabId) !== -1 && (
<Page view flow={flows.find((flow) => flow.id === tabId)!} />
)}
{currentFlow && <Page view flow={currentFlow} />}
</div>
);
}

View file

@ -5,9 +5,9 @@ import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { CONTROL_LOGIN_STATE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { getLoggedUser, onLogin } from "../../controllers/API";
import { onLogin } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import { LoginType } from "../../types/api";
import {
inputHandlerEventType,
@ -19,10 +19,10 @@ export default function LoginPage(): JSX.Element {
useState<loginInputStateType>(CONTROL_LOGIN_STATE);
const { password, username } = inputState;
const { login, getAuthentication, setUserData, setIsAdmin } =
const { login, isAuthenticated, setUserData, setIsAdmin } =
useContext(AuthContext);
const navigate = useNavigate();
const { setErrorData } = useContext(alertContext);
const setErrorData = useAlertStore((state) => state.setErrorData);
function handleInput({
target: { name, value },
@ -37,8 +37,7 @@ export default function LoginPage(): JSX.Element {
};
onLogin(user)
.then((user) => {
login(user.access_token, user.refresh_token);
getUser();
login(user.access_token);
navigate("/");
})
.catch((error) => {
@ -49,22 +48,6 @@ export default function LoginPage(): JSX.Element {
});
}
function getUser() {
if (getAuthentication()) {
setTimeout(() => {
getLoggedUser()
.then((user) => {
const isSuperUser = user!.is_superuser;
setIsAdmin(isSuperUser);
setUserData(user);
})
.catch((error) => {
console.log("login page", error);
});
}, 500);
}
}
return (
<Form.Root
onSubmit={(event) => {

View file

@ -1,5 +1,5 @@
import * as Form from "@radix-ui/react-form";
import { FormEvent, useContext, useEffect, useState } from "react";
import { FormEvent, useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
@ -8,8 +8,8 @@ import {
CONTROL_INPUT_STATE,
SIGN_UP_SUCCESS,
} from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { addUser } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import {
UserInputType,
inputHandlerEventType,
@ -23,7 +23,8 @@ export default function SignUp(): JSX.Element {
const [isDisabled, setDisableBtn] = useState<boolean>(true);
const { password, cnfPassword, username } = inputState;
const { setErrorData, setSuccessData } = useContext(alertContext);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const navigate = useNavigate();
function handleInput({

View file

@ -0,0 +1,95 @@
import { uniqueId } from "lodash";
import { create } from "zustand";
import { AlertItemType } from "../types/alerts";
import { AlertStoreType } from "../types/zustand/alert";
const pushNotificationList = (
list: AlertItemType[],
notification: AlertItemType
) => {
list.unshift(notification);
return list;
};
const useAlertStore = create<AlertStoreType>((set, get) => ({
errorData: { title: "", list: [] },
errorOpen: false,
noticeData: { title: "", link: "" },
noticeOpen: false,
successData: { title: "" },
successOpen: false,
notificationCenter: false,
notificationList: [],
loading: true,
setErrorData: (newState: { title: string; list?: Array<string> }) => {
if (newState.title && newState.title !== "") {
set({
errorData: newState,
errorOpen: true,
notificationCenter: true,
notificationList: pushNotificationList(get().notificationList, {
type: "error",
title: newState.title,
list: newState.list,
id: uniqueId(),
}),
});
}
},
setErrorOpen: (newState: boolean) => {
set({ errorOpen: newState });
},
setNoticeData: (newState: { title: string; link?: string }) => {
if (newState.title && newState.title !== "") {
set({
noticeData: newState,
noticeOpen: true,
notificationCenter: true,
notificationList: pushNotificationList(get().notificationList, {
type: "notice",
title: newState.title,
link: newState.link,
id: uniqueId(),
}),
});
}
},
setNoticeOpen: (newState: boolean) => {
set({ noticeOpen: newState });
},
setSuccessData: (newState: { title: string }) => {
if (newState.title && newState.title !== "") {
set({
successData: newState,
successOpen: true,
notificationCenter: true,
notificationList: pushNotificationList(get().notificationList, {
type: "success",
title: newState.title,
id: uniqueId(),
}),
});
}
},
setSuccessOpen: (newState: boolean) => {
set({ successOpen: newState });
},
setNotificationCenter: (newState: boolean) => {
set({ notificationCenter: newState });
},
clearNotificationList: () => {
set({ notificationList: [] });
},
removeFromNotificationList: (index: string) => {
set({
notificationList: get().notificationList.filter(
(item) => item.id !== index
),
});
},
setLoading: (newState: boolean) => {
set({ loading: newState });
},
}));
export default useAlertStore;

View file

@ -0,0 +1,20 @@
import { create } from "zustand";
import { getRepoStars, getVersion } from "../controllers/API";
import { DarkStoreType } from "../types/zustand/dark";
export const useDarkStore = create<DarkStoreType>((set) => ({
dark: JSON.parse(window.localStorage.getItem("isDark")!) ?? false,
stars: 0,
version: "",
setDark: (dark) => set(() => ({ dark: dark })),
refreshVersion: () => {
getVersion().then((data) => {
set(() => ({ version: data.version }));
});
},
refreshStars: () => {
getRepoStars("logspace-ai", "langflow").then((res) => {
set(() => ({ stars: res }));
});
},
}));

View file

@ -0,0 +1,312 @@
import { cloneDeep } from "lodash";
import {
Edge,
EdgeChange,
Node,
NodeChange,
addEdge,
applyEdgeChanges,
applyNodeChanges,
} from "reactflow";
import { create } from "zustand";
import {
NodeDataType,
NodeType,
sourceHandleType,
targetHandleType,
} from "../types/flow";
import { FlowStoreType } from "../types/zustand/flow";
import {
cleanEdges,
getHandleId,
getNodeId,
scapeJSONParse,
scapedJSONStringfy,
} from "../utils/reactflowUtils";
import useFlowsManagerStore from "./flowsManagerStore";
// this is our useStore hook that we can use in our components to get parts of the store and call actions
const useFlowStore = create<FlowStoreType>((set, get) => ({
sseData: {},
flowState: undefined,
nodes: [],
edges: [],
isBuilding: false,
isPending: false,
isBuilt: false,
reactFlowInstance: null,
lastCopiedSelection: null,
setPending: (isPending) => {
set({ isPending });
},
resetFlow: ({ nodes, edges, viewport }) => {
set({
nodes,
edges,
flowState: undefined,
sseData: {},
isBuilt: false,
});
get().reactFlowInstance!.setViewport(viewport);
},
updateSSEData: (sseData) => {
set((state) => ({ sseData: { ...state.sseData, ...sseData } }));
},
setIsBuilding: (isBuilding) => {
set({ isBuilding });
},
setIsBuilt: (isBuilt) => {
set({ isBuilt });
},
setFlowState: (flowState) => {
const newFlowState =
typeof flowState === "function" ? flowState(get().flowState) : flowState;
if (newFlowState !== get().flowState) {
set(() => ({
flowState: newFlowState,
}));
}
},
setReactFlowInstance: (newState) => {
set({ reactFlowInstance: newState });
},
onNodesChange: (changes: NodeChange[]) => {
set({
nodes: applyNodeChanges(changes, get().nodes),
});
},
onEdgesChange: (changes: EdgeChange[]) => {
set({
edges: applyEdgeChanges(changes, get().edges),
});
},
setNodes: (change) => {
let newChange = typeof change === "function" ? change(get().nodes) : change;
let newEdges = cleanEdges(newChange, get().edges);
set({
edges: newEdges,
nodes: newChange,
flowState: undefined,
isBuilt: false,
sseData: {},
});
const flowsManager = useFlowsManagerStore.getState();
flowsManager.autoSaveCurrentFlow(
newChange,
newEdges,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
);
},
setEdges: (change) => {
let newChange = typeof change === "function" ? change(get().edges) : change;
set({
edges: newChange,
flowState: undefined,
isBuilt: false,
sseData: {},
});
const flowsManager = useFlowsManagerStore.getState();
flowsManager.autoSaveCurrentFlow(
get().nodes,
newChange,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
);
},
setNode: (id: string, change: Node | ((oldState: Node) => Node)) => {
let newChange =
typeof change === "function"
? change(get().nodes.find((node) => node.id === id)!)
: change;
get().setNodes((oldNodes) =>
oldNodes.map((node) => {
if (node.id === id) {
return newChange;
}
return node;
})
);
},
getNode: (id: string) => {
return get().nodes.find((node) => node.id === id);
},
deleteNode: (nodeId) => {
get().setNodes(
get().nodes.filter((node) =>
typeof nodeId === "string"
? node.id !== nodeId
: !nodeId.includes(node.id)
)
);
},
deleteEdge: (edgeId) => {
get().setEdges(
get().edges.filter((edge) =>
typeof edgeId === "string"
? edge.id !== edgeId
: !edgeId.includes(edge.id)
)
);
},
paste: (selection, position) => {
let minimumX = Infinity;
let minimumY = Infinity;
let idsMap = {};
let newNodes: Node<NodeDataType>[] = get().nodes;
let newEdges = get().edges;
selection.nodes.forEach((node: Node) => {
if (node.position.y < minimumY) {
minimumY = node.position.y;
}
if (node.position.x < minimumX) {
minimumX = node.position.x;
}
});
const insidePosition = position.paneX
? { x: position.paneX + position.x, y: position.paneY! + position.y }
: get().reactFlowInstance!.screenToFlowPosition({
x: position.x,
y: position.y,
});
selection.nodes.forEach((node: NodeType) => {
// Generate a unique node ID
let newId = getNodeId(node.data.type);
idsMap[node.id] = newId;
// Create a new node object
const newNode: NodeType = {
id: newId,
type: "genericNode",
position: {
x: insidePosition.x + node.position!.x - minimumX,
y: insidePosition.y + node.position!.y - minimumY,
},
data: {
...cloneDeep(node.data),
id: newId,
},
};
// Add the new node to the list of nodes in state
newNodes = newNodes
.map((node) => ({ ...node, selected: false }))
.concat({ ...newNode, selected: false });
});
set({ nodes: newNodes });
selection.edges.forEach((edge: Edge) => {
let source = idsMap[edge.source];
let target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle!
);
let sourceHandle = scapedJSONStringfy({
...sourceHandleObject,
id: source,
});
sourceHandleObject.id = source;
edge.data.sourceHandle = sourceHandleObject;
const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!
);
let targetHandle = scapedJSONStringfy({
...targetHandleObject,
id: target,
});
targetHandleObject.id = target;
edge.data.targetHandle = targetHandleObject;
let id = getHandleId(source, sourceHandle, target, targetHandle);
newEdges = addEdge(
{
source,
target,
sourceHandle,
targetHandle,
id,
data: cloneDeep(edge.data),
style: { stroke: "#555" },
className:
targetHandleObject.type === "Text"
? "stroke-gray-800 "
: "stroke-gray-900 ",
animated: targetHandleObject.type === "Text",
selected: false,
},
newEdges.map((edge) => ({ ...edge, selected: false }))
);
});
set({ edges: newEdges });
},
setLastCopiedSelection: (newSelection) => {
set({ lastCopiedSelection: newSelection });
},
cleanFlow: () => {
set({
nodes: [],
edges: [],
flowState: undefined,
sseData: {},
isBuilt: false,
getFilterEdge: [],
});
},
setFilterEdge: (newState) => {
set({ getFilterEdge: newState });
},
getFilterEdge: [],
onConnect: (connection) => {
let newEdges: Edge[] = [];
get().setEdges((oldEdges) => {
newEdges = addEdge(
{
...connection,
data: {
targetHandle: scapeJSONParse(connection.targetHandle!),
sourceHandle: scapeJSONParse(connection.sourceHandle!),
},
style: { stroke: "#555" },
className:
((scapeJSONParse(connection.targetHandle!) as targetHandleType)
.type === "Text"
? "stroke-foreground "
: "stroke-foreground ") + " stroke-connection",
animated:
(scapeJSONParse(connection.targetHandle!) as targetHandleType)
.type === "Text",
},
oldEdges
);
return newEdges;
});
useFlowsManagerStore
.getState()
.autoSaveCurrentFlow(
get().nodes,
newEdges,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
);
},
unselectAll: () => {
let newNodes = cloneDeep(get().nodes);
newNodes.forEach((node) => {
node.selected = false;
let newEdges = cleanEdges(newNodes, get().edges);
set({
nodes: newNodes,
edges: newEdges,
});
});
},
}));
export default useFlowStore;

View file

@ -0,0 +1,390 @@
import { AxiosError } from "axios";
import { Edge, Node, Viewport, XYPosition } from "reactflow";
import { create } from "zustand";
import {
deleteFlowFromDatabase,
readFlowsFromDatabase,
saveFlowToDatabase,
updateFlowInDatabase,
uploadFlowsToDatabase,
} from "../controllers/API";
import { FlowType, NodeDataType } from "../types/flow";
import {
FlowsManagerStoreType,
UseUndoRedoOptions,
} from "../types/zustand/flowsManager";
import {
addVersionToDuplicates,
createFlowComponent,
createNewFlow,
processDataFromFlow,
processFlows,
} from "../utils/reactflowUtils";
import useAlertStore from "./alertStore";
import { useDarkStore } from "./darkStore";
import useFlowStore from "./flowStore";
import { useTypesStore } from "./typesStore";
let saveTimeoutId: NodeJS.Timeout | null = null;
const defaultOptions: UseUndoRedoOptions = {
maxHistorySize: 100,
enableShortcuts: true,
};
const past = {};
const future = {};
const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
currentFlowId: "",
setCurrentFlowId: (currentFlowId: string) => {
set((state) => ({
currentFlowId,
currentFlow: state.flows.find((flow) => flow.id === currentFlowId),
}));
},
flows: [],
setFlows: (flows: FlowType[]) => {
set({
flows,
currentFlow: flows.find((flow) => flow.id === get().currentFlowId),
});
},
currentFlow: undefined,
isLoading: true,
setIsLoading: (isLoading: boolean) => set({ isLoading }),
refreshFlows: () => {
return new Promise<void>((resolve, reject) => {
set({ isLoading: true });
readFlowsFromDatabase()
.then((dbData) => {
if (dbData) {
const { data, flows } = processFlows(dbData, false);
get().setFlows(flows);
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
}));
resolve();
}
})
.catch((e) => {
useAlertStore.getState().setErrorData({
title: "Could not load flows from database",
});
reject(e);
});
});
},
autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => {
// Clear the previous timeout if it exists.
if (saveTimeoutId) {
clearTimeout(saveTimeoutId);
}
// Set up a new timeout.
saveTimeoutId = setTimeout(() => {
if (get().currentFlow) {
get().saveFlow(
{ ...get().currentFlow!, data: { nodes, edges, viewport } },
true
);
}
}, 300); // Delay of 300ms.
},
saveFlow: (flow: FlowType, silent?: boolean) => {
return new Promise<void>((resolve, reject) => {
updateFlowInDatabase(flow)
.then((updatedFlow) => {
if (updatedFlow) {
// updates flow in state
if (!silent) {
useAlertStore
.getState()
.setSuccessData({ title: "Changes saved successfully" });
}
get().setFlows(
get().flows.map((flow) => {
if (flow.id === updatedFlow.id) {
return updatedFlow;
}
return flow;
})
);
//update tabs state
resolve();
}
})
.catch((err) => {
useAlertStore.getState().setErrorData({
title: "Error while saving changes",
list: [(err as AxiosError).message],
});
reject(err);
});
});
},
uploadFlows: () => {
return new Promise<void>((resolve) => {
const input = document.createElement("input");
input.type = "file";
// add a change event listener to the file input
input.onchange = (event: Event) => {
// check if the file type is application/json
if (
(event.target as HTMLInputElement).files![0].type ===
"application/json"
) {
// get the file from the file input
const file = (event.target as HTMLInputElement).files![0];
// read the file as text
const formData = new FormData();
formData.append("file", file);
uploadFlowsToDatabase(formData).then(() => {
get()
.refreshFlows()
.then(() => {
resolve();
});
});
}
};
// trigger the file input click event to open the file dialog
input.click();
});
},
addFlow: async (
newProject: Boolean,
flow?: FlowType,
override?: boolean,
position?: XYPosition
): Promise<string | undefined> => {
if (newProject) {
let flowData = flow
? processDataFromFlow(flow)
: { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
// Create a new flow with a default name if no flow is provided.
if (override) {
get().deleteComponent(flow!.name);
const newFlow = createNewFlow(flowData!, flow!);
const { id } = await saveFlowToDatabase(newFlow);
newFlow.id = id;
//setTimeout to prevent update state with wrong state
setTimeout(() => {
const { data, flows } = processFlows([newFlow, ...get().flows]);
get().setFlows(flows);
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
}));
}, 200);
// addFlowToLocalState(newFlow);
return;
}
const newFlow = createNewFlow(flowData!, flow!);
const newName = addVersionToDuplicates(newFlow, get().flows);
newFlow.name = newName;
try {
const { id } = await saveFlowToDatabase(newFlow);
// Change the id to the new id.
newFlow.id = id;
// Add the new flow to the list of flows.
const { data, flows } = processFlows([newFlow, ...get().flows]);
get().setFlows(flows);
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
}));
// Return the id
return id;
} catch (error) {
// Handle the error if needed
throw error; // Re-throw the error so the caller can handle it if needed
}
} else {
useFlowStore
.getState()
.paste(
{ nodes: flow!.data!.nodes, edges: flow!.data!.edges },
position ?? { x: 10, y: 10 }
);
}
},
removeFlow: async (id: string) => {
return new Promise<void>((resolve) => {
const index = get().flows.findIndex((flow) => flow.id === id);
if (index >= 0) {
deleteFlowFromDatabase(id).then(() => {
const { data, flows } = processFlows(
get().flows.filter((flow) => flow.id !== id)
);
get().setFlows(flows);
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
}));
resolve();
});
}
});
},
deleteComponent: async (key: string) => {
return new Promise<void>((resolve) => {
let componentFlow = get().flows.find(
(componentFlow) =>
componentFlow.is_component && componentFlow.name === key
);
if (componentFlow) {
get()
.removeFlow(componentFlow.id)
.then(() => {
resolve();
});
}
});
},
uploadFlow: async ({
newProject,
file,
isComponent = false,
position = { x: 10, y: 10 },
}: {
newProject: boolean;
file?: File;
isComponent?: boolean;
position?: XYPosition;
}): Promise<string | never> => {
return new Promise(async (resolve, reject) => {
let id;
if (file) {
let text = await file.text();
let fileData = JSON.parse(text);
if (
newProject &&
((!fileData.is_component && isComponent === true) ||
(fileData.is_component !== undefined &&
fileData.is_component !== isComponent))
) {
reject("You cannot upload a component as a flow or vice versa");
} else {
if (fileData.flows) {
fileData.flows.forEach((flow: FlowType) => {
id = get().addFlow(newProject, flow, undefined, position);
});
resolve("");
} else {
id = await get().addFlow(newProject, fileData, undefined, position);
resolve(id);
}
}
} else {
// create a file input
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
// add a change event listener to the file input
input.onchange = async (e: Event) => {
if (
(e.target as HTMLInputElement).files![0].type === "application/json"
) {
const currentfile = (e.target as HTMLInputElement).files![0];
let text = await currentfile.text();
let fileData: FlowType = await JSON.parse(text);
if (
(!fileData.is_component && isComponent === true) ||
(fileData.is_component !== undefined &&
fileData.is_component !== isComponent)
) {
reject("You cannot upload a component as a flow or vice versa");
} else {
id = await get().addFlow(newProject, fileData);
resolve(id);
}
}
};
// trigger the file input click event to open the file dialog
input.click();
}
});
},
saveComponent: (component: NodeDataType, override: boolean) => {
component.node!.official = false;
return get().addFlow(
true,
createFlowComponent(component, useDarkStore.getState().version),
override
);
},
takeSnapshot: () => {
const currentFlowId = get().currentFlowId;
// push the current graph to the past state
const newState = useFlowStore.getState();
const pastLength = past[currentFlowId]?.length ?? 0;
if (
pastLength > 0 &&
JSON.stringify(past[currentFlowId][pastLength - 1]) !==
JSON.stringify(newState)
) {
past[currentFlowId] = past[currentFlowId].slice(
pastLength - defaultOptions.maxHistorySize + 1,
pastLength
);
past[currentFlowId].push(newState);
} else {
past[currentFlowId] = [newState];
}
future[currentFlowId] = [];
},
undo: () => {
const newState = useFlowStore.getState();
const currentFlowId = get().currentFlowId;
const pastLength = past[currentFlowId]?.length ?? 0;
const pastState = past[currentFlowId]?.[pastLength - 1] ?? null;
if (pastState) {
past[currentFlowId] = past[currentFlowId].slice(0, pastLength - 1);
if (!future[currentFlowId]) future[currentFlowId] = [];
future[currentFlowId].push({
nodes: newState.nodes,
edges: newState.edges,
});
newState.setNodes(pastState.nodes);
newState.setEdges(pastState.edges);
}
},
redo: () => {
const newState = useFlowStore.getState();
const currentFlowId = get().currentFlowId;
const futureLength = future[currentFlowId]?.length ?? 0;
const futureState = future[currentFlowId]?.[futureLength - 1] ?? null;
if (futureState) {
future[currentFlowId] = future[currentFlowId].slice(0, futureLength - 1);
if (!past[currentFlowId]) past[currentFlowId] = [];
past[currentFlowId].push({
nodes: newState.nodes,
edges: newState.edges,
});
newState.setNodes(futureState.nodes);
newState.setEdges(futureState.edges);
}
},
}));
export default useFlowsManagerStore;

View file

@ -0,0 +1,37 @@
import { create } from "zustand";
import { checkHasApiKey, checkHasStore } from "../controllers/API";
import { StoreStoreType } from "../types/zustand/store";
export const useStoreStore = create<StoreStoreType>((set) => ({
hasStore: true,
validApiKey: false,
hasApiKey: false,
loadingApiKey: true,
updateHasStore: (hasStore) => set(() => ({ hasStore: hasStore })),
updateValidApiKey: (validApiKey) => set(() => ({ validApiKey: validApiKey })),
updateLoadingApiKey: (loadingApiKey) =>
set(() => ({ loadingApiKey: loadingApiKey })),
updateHasApiKey: (hasApiKey) => set(() => ({ hasApiKey: hasApiKey })),
}));
checkHasStore().then((res) => {
useStoreStore.setState({ hasStore: res?.enabled ?? false });
});
const fetchApiData = async () => {
useStoreStore.setState({ loadingApiKey: true });
try {
const res = await checkHasApiKey();
useStoreStore.setState({
loadingApiKey: false,
validApiKey: res?.is_valid ?? false,
hasApiKey: res?.has_api_key ?? false,
});
} catch (e) {
useStoreStore.setState({ loadingApiKey: false });
console.log(e);
}
};
fetchApiData();

View file

@ -0,0 +1,46 @@
import { create } from "zustand";
import { getAll } from "../controllers/API";
import { APIDataType } from "../types/api";
import { TypesStoreType } from "../types/zustand/types";
import { templatesGenerator, typesGenerator } from "../utils/reactflowUtils";
import useAlertStore from "./alertStore";
export const useTypesStore = create<TypesStoreType>((set, get) => ({
types: {},
templates: {},
data: {},
getTypes: () => {
return new Promise<void>(async (resolve, reject) => {
getAll()
.then((response) => {
const data = response.data;
useAlertStore.setState({ loading: false });
set((old) => ({
types: typesGenerator(data),
data: { ...old.data, ...data },
templates: templatesGenerator(data),
}));
resolve();
})
.catch((error) => {
useAlertStore.getState().setErrorData({
title: "An error has occurred while fetching types.",
list: ["Please refresh the page."],
});
console.error("An error has occurred while fetching types.");
console.log(error);
reject();
});
});
},
setTypes: (newState: {}) => {
set({ types: newState });
},
setTemplates: (newState: {}) => {
set({ templates: newState });
},
setData: (change: APIDataType | ((old: APIDataType) => APIDataType)) => {
let newChange = typeof change === "function" ? change(get().data) : change;
set({ data: newChange });
},
}));

View file

@ -2,8 +2,8 @@ import { Edge, Node, Viewport } from "reactflow";
import { FlowType } from "../flow";
//kind and class are just representative names to represent the actual structure of the object received by the API
export type APIDataType = { [key: string]: APIKindType };
export type APIObjectType = { kind: APIKindType; [key: string]: APIKindType };
export type APIKindType = { class: APIClassType; [key: string]: APIClassType };
export type APIObjectType = { [key: string]: APIKindType };
export type APIKindType = { [key: string]: APIClassType };
export type APITemplateType = {
[key: string]: TemplateVariableType;
};

View file

@ -1,7 +1,6 @@
import { ReactFlowInstance } from "reactflow";
import { FlowType } from "../flow";
export type ChatType = { flow: FlowType; reactFlowInstance: ReactFlowInstance };
export type ChatType = { flow: FlowType };
export type ChatMessageType = {
message: string | Object;
template?: string;

View file

@ -3,7 +3,6 @@ import { ReactFlowJsonObject, XYPosition } from "reactflow";
import { APIClassType, APITemplateType, TemplateVariableType } from "../api";
import { ChatMessageType } from "../chat";
import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index";
import { typesContextType } from "../typesContext";
import { sourceHandleType, targetHandleType } from "./../flow/index";
export type InputComponentType = {
autoFocus?: boolean;
@ -48,13 +47,13 @@ export type ParameterComponentType = {
required?: boolean;
name?: string;
tooltipTitle: string | undefined;
dataContext?: typesContextType;
optionalHandle?: Array<String> | null;
info?: string;
proxy?: { field: string; id: string };
showNode?: boolean;
index?: string;
onCloseModal?: (close: boolean) => void;
isMinimized?: boolean;
};
export type InputListComponentType = {
value: string[];
@ -264,12 +263,12 @@ export type LoadingComponentProps = {
export type ContentProps = {
children: ReactNode;
tolltipContent?: ReactNode;
side?: "top" | "right" | "bottom" | "left";
};
export type HeaderProps = { children: ReactNode; description: string };
export type TriggerProps = {
children: ReactNode;
tooltipContent?: ReactNode;
side?: "top" | "right" | "bottom" | "left";
};
export interface languageMap {
@ -301,15 +300,13 @@ export type ConfirmationModalType = {
onCancel?: () => void;
title: string;
titleHeader?: string;
asChild?: boolean;
destructive?: boolean;
modalContentTitle?: string;
cancelText: string;
confirmationText: string;
children: [
React.ReactElement<ContentProps>,
React.ReactElement<TriggerProps>
];
children:
| [React.ReactElement<ContentProps>, React.ReactElement<TriggerProps>]
| React.ReactElement<ContentProps>;
icon: string;
data?: any;
index?: number;
@ -441,11 +438,6 @@ export type headerFlowsType = {
style?: FlowStyleType;
};
export type menuBarPropsType = {
flows: Array<headerFlowsType>;
tabId: string;
};
export type chatInputType = {
chatValue: string;
inputRef: {
@ -488,6 +480,7 @@ export type nodeToolbarPropsType = {
setShowNode: (boolean: any) => void;
numberOfHandles: boolean[] | [];
showNode: boolean;
setIsMinimized: (boolean: boolean) => void;
};
export type parsedDataType = {

View file

@ -5,12 +5,10 @@ export type AuthContextType = {
setIsAdmin: (isAdmin: boolean) => void;
isAuthenticated: boolean;
accessToken: string | null;
refreshToken: string | null;
login: (accessToken: string, refreshToken: string) => void;
logout: () => void;
login: (accessToken: string) => void;
logout: () => Promise<void>;
userData: Users | null;
setUserData: (userData: Users | null) => void;
getAuthentication: () => boolean;
authenticationErrorCount: number;
autoLogin: boolean;
setAutoLogin: (autoLogin: boolean) => void;

View file

@ -1,9 +0,0 @@
export type storeContextType = {
setHasStore: (store: boolean) => void;
hasStore: boolean;
setHasApiKey: (key: boolean) => void;
hasApiKey: boolean;
setValidApiKey: (key: boolean) => void;
validApiKey: boolean;
loadingApiKey: boolean;
};

View file

@ -1,32 +1,35 @@
import { XYPosition } from "reactflow";
import { tweakType } from "../components";
import { FlowType, NodeDataType } from "../flow";
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
export type FlowsContextType = {
saveFlow: (flow: FlowType, silent?: boolean) => Promise<void>;
//keep
saveFlow: (flow?: FlowType, silent?: boolean) => Promise<void>;
tabId: string;
//keep
isLoading: boolean;
setTabId: (index: string) => void;
flows: Array<FlowType>;
//keep
removeFlow: (id: string) => void;
refreshFlows: () => void;
//keep
addFlow: (
newProject: boolean,
flow?: FlowType,
override?: boolean,
position?: XYPosition
) => Promise<String | undefined>;
updateFlow: (newFlow: FlowType) => void;
incrementNodeId: () => string;
downloadFlow: (
flow: FlowType,
flowName: string,
flowDescription?: string
) => void;
//keep
downloadFlows: () => void;
//keep
uploadFlows: () => void;
isBuilt: boolean;
setIsBuilt: (state: boolean) => void;
saveCurrentFlow: () => void;
setVersion: (version: string) => void;
uploadFlow: ({
newProject,
file,
@ -38,38 +41,28 @@ export type FlowsContextType = {
isComponent?: boolean;
position?: XYPosition;
}) => Promise<String | never>;
hardReset: () => void;
getNodeId: (nodeType: string) => string;
tabsState: FlowsState;
setTabsState: (state: FlowsState) => void;
paste: (
selection: { nodes: any; edges: any },
position: { x: number; y: number; paneX?: number; paneY?: number }
setTabsState: (
update: FlowsState | ((oldState: FlowsState) => FlowsState)
) => void;
lastCopiedSelection: { nodes: any; edges: any } | null;
setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void;
setTweak: (tweak: tweakType) => tweakType | void;
getTweak: tweakType;
saveComponent: (
component: NodeDataType,
override: boolean
) => Promise<String | undefined>;
deleteComponent: (key: string) => void;
version: string;
nodesOnFlow: string;
setNodesOnFlow: (nodes: string) => void;
flows: Array<FlowType>;
};
export type FlowsState = {
[key: string]: {
isPending: boolean;
formKeysData: {
template?: string;
input_keys?: Object;
memory_keys?: Array<string>;
handle_keys?: Array<string>;
};
};
[key: string]: FlowState | undefined;
};
export type FlowState = {
template?: string;
input_keys?: Object;
memory_keys?: Array<string>;
handle_keys?: Array<string>;
};
export type errorsVarType = {

View file

@ -1,111 +0,0 @@
import { Edge, Node, ReactFlowInstance } from "reactflow";
import { AlertItemType } from "../alerts";
import { APIClassType, APIDataType } from "../api";
const types: { [char: string]: string } = {};
const template: { [char: string]: APIClassType } = {};
const data: { [char: string]: string } = {};
export type typesContextType = {
reactFlowInstance: ReactFlowInstance | null;
setReactFlowInstance: (newState: ReactFlowInstance) => void;
deleteNode: (idx: string | Array<string>) => void;
types: typeof types;
setTypes: (newState: {}) => void;
templates: typeof template;
setTemplates: (newState: {}) => void;
data: APIDataType;
setData: (newState: {}) => void;
fetchError: boolean;
setFetchError: (newState: boolean) => void;
setFilterEdge: (newState) => void;
getFilterEdge: any[];
deleteEdge: (idx: string | Array<string>) => void;
};
export type alertContextType = {
errorData: { title: string; list?: Array<string> };
setErrorData: (newState: { title: string; list?: Array<string> }) => void;
errorOpen: boolean;
setErrorOpen: (newState: boolean) => void;
noticeData: { title: string; link?: string };
setNoticeData: (newState: { title: string; link?: string }) => void;
noticeOpen: boolean;
setNoticeOpen: (newState: boolean) => void;
successData: { title: string };
setSuccessData: (newState: { title: string }) => void;
successOpen: boolean;
setSuccessOpen: (newState: boolean) => void;
notificationCenter: boolean;
setNotificationCenter: (newState: boolean) => void;
notificationList: Array<AlertItemType>;
pushNotificationList: (Object: AlertItemType) => void;
clearNotificationList: () => void;
removeFromNotificationList: (index: string) => void;
loading: boolean;
setLoading: (newState: boolean) => void;
modalContextOpen: boolean | null;
setModalContextOpen: (newState: boolean) => void;
};
export type darkContextType = {
dark: {};
setDark: (newState: {}) => void;
stars: number;
setStars: (stars: number) => void;
gradientIndex: number;
setGradientIndex: (index: number) => void;
};
export type locationContextType = {
current: Array<string>;
setCurrent: (newState: Array<string>) => void;
isStackedOpen: boolean;
setIsStackedOpen: (newState: boolean) => void;
showSideBar: boolean;
setShowSideBar: (newState: boolean) => void;
extraNavigation: {
title: string;
options?: Array<{
name: string;
href: string;
icon: React.ElementType;
children?: Array<JSX.Element>;
}>;
};
setExtraNavigation: (newState: {
title: string;
options?: Array<{
name: string;
href: string;
icon: React.ElementType;
children?: Array<JSX.Element>;
}>;
}) => void;
extraComponent: any;
setExtraComponent: (newState: JSX.Element) => void;
};
export type undoRedoContextType = {
undo: () => void;
redo: () => void;
takeSnapshot: () => void;
};
export type UseUndoRedoOptions = {
maxHistorySize: number;
enableShortcuts: boolean;
};
export type UseUndoRedo = (options?: UseUndoRedoOptions) => {
undo: () => void;
redo: () => void;
takeSnapshot: () => void;
canUndo: boolean;
canRedo: boolean;
};
export type HistoryItem = {
nodes: Node[];
edges: Edge[];
};

View file

@ -1,14 +1,6 @@
import { Edge, Node } from "reactflow";
import { FlowType, NodeType } from "../flow";
export type cleanEdgesType = {
flow: {
edges: Edge[];
nodes: NodeType[];
};
updateEdge: (edge: Edge[]) => void;
};
export type unselectAllNodesType = {
updateNodes: (nodes: Node[]) => void;
data: Node[];

View file

@ -0,0 +1,23 @@
import { AlertItemType } from "../../alerts";
export type AlertStoreType = {
errorData: { title: string; list?: Array<string> };
setErrorData: (newState: { title: string; list?: Array<string> }) => void;
errorOpen: boolean;
setErrorOpen: (newState: boolean) => void;
noticeData: { title: string; link?: string };
setNoticeData: (newState: { title: string; link?: string }) => void;
noticeOpen: boolean;
setNoticeOpen: (newState: boolean) => void;
successData: { title: string };
setSuccessData: (newState: { title: string }) => void;
successOpen: boolean;
setSuccessOpen: (newState: boolean) => void;
notificationCenter: boolean;
setNotificationCenter: (newState: boolean) => void;
notificationList: Array<AlertItemType>;
clearNotificationList: () => void;
removeFromNotificationList: (index: string) => void;
loading: boolean;
setLoading: (newState: boolean) => void;
};

View file

@ -0,0 +1,8 @@
export type DarkStoreType = {
dark: boolean;
stars: number;
version: string;
setDark: (dark: boolean) => void;
refreshVersion: () => void;
refreshStars: () => void;
};

View file

@ -0,0 +1,58 @@
import {
Connection,
Edge,
Node,
OnEdgesChange,
OnNodesChange,
ReactFlowInstance,
Viewport,
} from "reactflow";
import { FlowState } from "../../tabs";
export type FlowStoreType = {
updateSSEData: (sseData: object) => void;
sseData: object;
isBuilding: boolean;
isPending: boolean;
setIsBuilding: (isBuilding: boolean) => void;
setPending: (isPending: boolean) => void;
resetFlow: (flow: {
nodes: Node[];
edges: Edge[];
viewport: Viewport;
}) => void;
reactFlowInstance: ReactFlowInstance | null;
setReactFlowInstance: (newState: ReactFlowInstance) => void;
flowState: FlowState | undefined;
setFlowState: (
state:
| FlowState
| undefined
| ((oldState: FlowState | undefined) => FlowState)
) => void;
nodes: Node[];
edges: Edge[];
onNodesChange: OnNodesChange;
onEdgesChange: OnEdgesChange;
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void;
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void;
setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void;
getNode: (id: string) => Node | undefined;
deleteNode: (nodeId: string | Array<string>) => void;
deleteEdge: (edgeId: string | Array<string>) => void;
paste: (
selection: { nodes: any; edges: any },
position: { x: number; y: number; paneX?: number; paneY?: number }
) => void;
lastCopiedSelection: { nodes: any; edges: any } | null;
setLastCopiedSelection: (
newSelection: { nodes: any; edges: any } | null
) => void;
isBuilt: boolean;
setIsBuilt: (isBuilt: boolean) => void;
cleanFlow: () => void;
setFilterEdge: (newState) => void;
getFilterEdge: any[];
onConnect: (connection: Connection) => void;
unselectAll: () => void;
};

View file

@ -0,0 +1,51 @@
import { Edge, Node, Viewport, XYPosition } from "reactflow";
import { FlowType } from "../../flow";
export type FlowsManagerStoreType = {
flows: Array<FlowType>;
setFlows: (flows: FlowType[]) => void;
currentFlow: FlowType | undefined;
currentFlowId: string;
setCurrentFlowId: (currentFlowId: string) => void;
isLoading: boolean;
setIsLoading: (isLoading: boolean) => void;
refreshFlows: () => Promise<void>;
saveFlow: (flow: FlowType, silent?: boolean) => Promise<void>;
autoSaveCurrentFlow: (
nodes: Node[],
edges: Edge[],
viewport: Viewport
) => void;
uploadFlows: () => Promise<void>;
uploadFlow: ({
newProject,
file,
isComponent,
position,
}: {
newProject: boolean;
file?: File;
isComponent?: boolean;
position?: XYPosition;
}) => Promise<string | never>;
addFlow: (
newProject: boolean,
flow?: FlowType,
override?: boolean,
position?: XYPosition
) => Promise<string | undefined>;
deleteComponent: (key: string) => Promise<void>;
removeFlow: (id: string) => Promise<void>;
saveComponent: (
component: any,
override: boolean
) => Promise<string | undefined>;
undo: () => void;
redo: () => void;
takeSnapshot: () => void;
};
export type UseUndoRedoOptions = {
maxHistorySize: number;
enableShortcuts: boolean;
};

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