Merge branch 'cz/bug/state/zustand' into update_lc
This commit is contained in:
commit
4591824a63
94 changed files with 2443 additions and 3090 deletions
|
|
@ -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 fastapi.security import OAuth2PasswordRequestForm
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
||||||
|
|
@ -16,6 +16,7 @@ router = APIRouter(tags=["Login"])
|
||||||
|
|
||||||
@router.post("/login", response_model=Token)
|
@router.post("/login", response_model=Token)
|
||||||
async def login_to_get_access_token(
|
async def login_to_get_access_token(
|
||||||
|
response: Response,
|
||||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||||
db: Session = Depends(get_session),
|
db: Session = Depends(get_session),
|
||||||
# _: Session = Depends(get_current_active_user)
|
# _: Session = Depends(get_current_active_user)
|
||||||
|
|
@ -31,7 +32,10 @@ async def login_to_get_access_token(
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
if user:
|
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:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
|
@ -41,9 +45,13 @@ async def login_to_get_access_token(
|
||||||
|
|
||||||
|
|
||||||
@router.get("/auto_login")
|
@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:
|
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(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
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")
|
@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:
|
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:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
detail="Invalid refresh token",
|
detail="Invalid refresh token",
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
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"}
|
||||||
|
|
|
||||||
|
|
@ -15,29 +15,21 @@ class VectaraSelfQueryRetriverComponent(CustomComponent):
|
||||||
|
|
||||||
display_name: str = "Vectara Self Query Retriever for Vectara Vector Store"
|
display_name: str = "Vectara Self Query Retriever for Vectara Vector Store"
|
||||||
description: str = "Implementation of Vectara Self Query Retriever"
|
description: str = "Implementation of Vectara Self Query Retriever"
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/retrievers/self_query/vectara_self_query"
|
||||||
"https://python.langchain.com/docs/integrations/retrievers/self_query/vectara_self_query"
|
|
||||||
)
|
|
||||||
beta = True
|
beta = True
|
||||||
|
|
||||||
field_config = {
|
field_config = {
|
||||||
"code": {"show": True},
|
"code": {"show": True},
|
||||||
"vectorstore": {
|
"vectorstore": {"display_name": "Vector Store", "info": "Input Vectara Vectore Store"},
|
||||||
"display_name": "Vector Store",
|
"llm": {"display_name": "LLM", "info": "For self query retriever"},
|
||||||
"info": "Input Vectara Vectore Store"
|
"document_content_description": {
|
||||||
},
|
"display_name": "Document Content Description",
|
||||||
"llm": {
|
|
||||||
"display_name": "LLM",
|
|
||||||
"info": "For self query retriever"
|
|
||||||
},
|
|
||||||
"document_content_description":{
|
|
||||||
"display_name": "Document Content Description",
|
|
||||||
"info": "For self query retriever",
|
"info": "For self query retriever",
|
||||||
},
|
},
|
||||||
"metadata_field_info": {
|
"metadata_field_info": {
|
||||||
"display_name": "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)",
|
"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(
|
def build(
|
||||||
|
|
@ -47,24 +39,19 @@ class VectaraSelfQueryRetriverComponent(CustomComponent):
|
||||||
llm: BaseLanguageModel,
|
llm: BaseLanguageModel,
|
||||||
metadata_field_info: List[str],
|
metadata_field_info: List[str],
|
||||||
) -> BaseRetriever:
|
) -> BaseRetriever:
|
||||||
|
|
||||||
metadata_field_obj = []
|
metadata_field_obj = []
|
||||||
|
|
||||||
for meta in metadata_field_info:
|
for meta in metadata_field_info:
|
||||||
meta_obj = json.loads(meta)
|
meta_obj = json.loads(meta)
|
||||||
if 'name' not in meta_obj or 'description' not in meta_obj or 'type' not in meta_obj :
|
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.')
|
raise Exception("Incorrect metadata field info format.")
|
||||||
attribute_info = AttributeInfo(
|
attribute_info = AttributeInfo(
|
||||||
name = meta_obj['name'],
|
name=meta_obj["name"],
|
||||||
description = meta_obj['description'],
|
description=meta_obj["description"],
|
||||||
type = meta_obj['type'],
|
type=meta_obj["type"],
|
||||||
)
|
)
|
||||||
metadata_field_obj.append(attribute_info)
|
metadata_field_obj.append(attribute_info)
|
||||||
|
|
||||||
return SelfQueryRetriever.from_llm(
|
return SelfQueryRetriever.from_llm(
|
||||||
llm,
|
llm, vectorstore, document_content_description, metadata_field_obj, verbose=True
|
||||||
vectorstore,
|
)
|
||||||
document_content_description,
|
|
||||||
metadata_field_obj,
|
|
||||||
verbose=True
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,7 @@
|
||||||
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
|
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
|
||||||
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
|
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
|
||||||
{ "name": "Connection", "value": "keep-alive" },
|
{ "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": "Host", "value": "localhost:3000" },
|
||||||
{ "name": "Referer", "value": "http://localhost:3000/flows" },
|
{ "name": "Referer", "value": "http://localhost:3000/flows" },
|
||||||
{ "name": "Sec-Fetch-Dest", "value": "empty" },
|
{ "name": "Sec-Fetch-Dest", "value": "empty" },
|
||||||
|
|
@ -338,7 +338,7 @@
|
||||||
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
|
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
|
||||||
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
|
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
|
||||||
{ "name": "Connection", "value": "keep-alive" },
|
{ "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": "Host", "value": "localhost:3000" },
|
||||||
{ "name": "Referer", "value": "http://localhost:3000/flows" },
|
{ "name": "Referer", "value": "http://localhost:3000/flows" },
|
||||||
{ "name": "Sec-Fetch-Dest", "value": "empty" },
|
{ "name": "Sec-Fetch-Dest", "value": "empty" },
|
||||||
|
|
@ -392,7 +392,7 @@
|
||||||
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
|
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
|
||||||
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
|
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
|
||||||
{ "name": "Connection", "value": "keep-alive" },
|
{ "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": "Host", "value": "localhost:3000" },
|
||||||
{ "name": "Referer", "value": "http://localhost:3000/flows" },
|
{ "name": "Referer", "value": "http://localhost:3000/flows" },
|
||||||
{ "name": "Sec-Fetch-Dest", "value": "empty" },
|
{ "name": "Sec-Fetch-Dest", "value": "empty" },
|
||||||
|
|
@ -446,7 +446,7 @@
|
||||||
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
|
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
|
||||||
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
|
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
|
||||||
{ "name": "Connection", "value": "keep-alive" },
|
{ "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": "Host", "value": "localhost:3000" },
|
||||||
{ "name": "Referer", "value": "http://localhost:3000/flow/2920dde2-5c24-4fe0-9c06-ef86b5a16a99" },
|
{ "name": "Referer", "value": "http://localhost:3000/flow/2920dde2-5c24-4fe0-9c06-ef86b5a16a99" },
|
||||||
{ "name": "Sec-Fetch-Dest", "value": "empty" },
|
{ "name": "Sec-Fetch-Dest", "value": "empty" },
|
||||||
|
|
@ -500,7 +500,7 @@
|
||||||
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
|
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
|
||||||
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
|
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
|
||||||
{ "name": "Connection", "value": "keep-alive" },
|
{ "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": "Host", "value": "localhost:3000" },
|
||||||
{ "name": "Referer", "value": "http://localhost:3000/flow/2920dde2-5c24-4fe0-9c06-ef86b5a16a99" },
|
{ "name": "Referer", "value": "http://localhost:3000/flow/2920dde2-5c24-4fe0-9c06-ef86b5a16a99" },
|
||||||
{ "name": "Sec-Fetch-Dest", "value": "empty" },
|
{ "name": "Sec-Fetch-Dest", "value": "empty" },
|
||||||
|
|
@ -554,7 +554,7 @@
|
||||||
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
|
{ "name": "Accept-Language", "value": "en-US,en;q=0.9" },
|
||||||
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
|
{ "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMjUzYmZiYS02MzY4LTQ0ZGMtODVmNy0wZDZkYTllNDU5NjgiLCJleHAiOjE3MzM4NTY4OTh9.5MFFb0JCck3ITSKXbxhwO9yAscnXcwXNTV70ZYBRB20" },
|
||||||
{ "name": "Connection", "value": "keep-alive" },
|
{ "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": "Host", "value": "localhost:3000" },
|
||||||
{ "name": "Referer", "value": "http://localhost:3000/flow/2920dde2-5c24-4fe0-9c06-ef86b5a16a99" },
|
{ "name": "Referer", "value": "http://localhost:3000/flow/2920dde2-5c24-4fe0-9c06-ef86b5a16a99" },
|
||||||
{ "name": "Sec-Fetch-Dest", "value": "empty" },
|
{ "name": "Sec-Fetch-Dest", "value": "empty" },
|
||||||
|
|
|
||||||
55
src/frontend/package-lock.json
generated
55
src/frontend/package-lock.json
generated
|
|
@ -13,6 +13,7 @@
|
||||||
"@headlessui/react": "^1.7.17",
|
"@headlessui/react": "^1.7.17",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"@mui/material": "^5.14.7",
|
"@mui/material": "^5.14.7",
|
||||||
|
"@preact/signals-react": "^2.0.0",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.4",
|
"@radix-ui/react-dialog": "^1.0.4",
|
||||||
|
|
@ -71,7 +72,8 @@
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vite-plugin-svgr": "^3.2.0",
|
"vite-plugin-svgr": "^3.2.0",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4",
|
||||||
|
"zustand": "^4.4.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.38.0",
|
"@playwright/test": "^1.38.0",
|
||||||
|
|
@ -1465,6 +1467,31 @@
|
||||||
"url": "https://opencollective.com/popperjs"
|
"url": "https://opencollective.com/popperjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@preact/signals-core": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-dE6f+WCX5ZUDwXzUIWNMhhglmuLpqJhuy3X3xHrhZYI0Hm2LyQwOu0l9mdPiWrVNsE+Q7txOnJPgtIqHCYoBVA==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@preact/signals-react": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@preact/signals-react/-/signals-react-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-tMVi2SXFXlojaiPNWa8dlYaidR/XvEgMSp+iymKJgMssBM/QVtUQrodKZek1BJju+dkVHiyeuQHmkuLOI9oyNw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@preact/signals-core": "^1.5.1",
|
||||||
|
"use-sync-external-store": "^1.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.14.0 || 17.x || 18.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/number": {
|
"node_modules/@radix-ui/number": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz",
|
||||||
|
|
@ -11719,9 +11746,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zustand": {
|
"node_modules/zustand": {
|
||||||
"version": "4.4.6",
|
"version": "4.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.7.tgz",
|
||||||
"integrity": "sha512-Rb16eW55gqL4W2XZpJh0fnrATxYEG3Apl2gfHTyDSE965x/zxslTikpNch0JgNjJA9zK6gEFW8Fl6d1rTZaqgg==",
|
"integrity": "sha512-QFJWJMdlETcI69paJwhSMJz7PPWjVP8Sjhclxmxmxv/RYI7ZOvR5BHX+ktH0we9gTWQMxcne8q1OY8xxz604gw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"use-sync-external-store": "1.2.0"
|
"use-sync-external-store": "1.2.0"
|
||||||
},
|
},
|
||||||
|
|
@ -12606,6 +12633,20 @@
|
||||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
|
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
|
||||||
},
|
},
|
||||||
|
"@preact/signals-core": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-dE6f+WCX5ZUDwXzUIWNMhhglmuLpqJhuy3X3xHrhZYI0Hm2LyQwOu0l9mdPiWrVNsE+Q7txOnJPgtIqHCYoBVA=="
|
||||||
|
},
|
||||||
|
"@preact/signals-react": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@preact/signals-react/-/signals-react-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-tMVi2SXFXlojaiPNWa8dlYaidR/XvEgMSp+iymKJgMssBM/QVtUQrodKZek1BJju+dkVHiyeuQHmkuLOI9oyNw==",
|
||||||
|
"requires": {
|
||||||
|
"@preact/signals-core": "^1.5.1",
|
||||||
|
"use-sync-external-store": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@radix-ui/number": {
|
"@radix-ui/number": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz",
|
||||||
|
|
@ -19291,9 +19332,9 @@
|
||||||
"integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg=="
|
"integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg=="
|
||||||
},
|
},
|
||||||
"zustand": {
|
"zustand": {
|
||||||
"version": "4.4.6",
|
"version": "4.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.7.tgz",
|
||||||
"integrity": "sha512-Rb16eW55gqL4W2XZpJh0fnrATxYEG3Apl2gfHTyDSE965x/zxslTikpNch0JgNjJA9zK6gEFW8Fl6d1rTZaqgg==",
|
"integrity": "sha512-QFJWJMdlETcI69paJwhSMJz7PPWjVP8Sjhclxmxmxv/RYI7ZOvR5BHX+ktH0we9gTWQMxcne8q1OY8xxz604gw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"use-sync-external-store": "1.2.0"
|
"use-sync-external-store": "1.2.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
"@headlessui/react": "^1.7.17",
|
"@headlessui/react": "^1.7.17",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"@mui/material": "^5.14.7",
|
"@mui/material": "^5.14.7",
|
||||||
|
"@preact/signals-react": "^2.0.0",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.4",
|
"@radix-ui/react-dialog": "^1.0.4",
|
||||||
|
|
@ -66,7 +67,8 @@
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vite-plugin-svgr": "^3.2.0",
|
"vite-plugin-svgr": "^3.2.0",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4",
|
||||||
|
"zustand": "^4.4.7"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:docker": "vite --host 0.0.0.0",
|
"dev:docker": "vite --host 0.0.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
|
||||||
import "reactflow/dist/style.css";
|
import "reactflow/dist/style.css";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
|
|
@ -15,37 +14,27 @@ import {
|
||||||
FETCH_ERROR_DESCRIPION,
|
FETCH_ERROR_DESCRIPION,
|
||||||
FETCH_ERROR_MESSAGE,
|
FETCH_ERROR_MESSAGE,
|
||||||
} from "./constants/constants";
|
} from "./constants/constants";
|
||||||
import { alertContext } from "./contexts/alertContext";
|
import { AuthContext } from "./contexts/authContext";
|
||||||
import { FlowsContext } from "./contexts/flowsContext";
|
import { getHealth } from "./controllers/API";
|
||||||
import { locationContext } from "./contexts/locationContext";
|
|
||||||
import { typesContext } from "./contexts/typesContext";
|
|
||||||
import Router from "./routes";
|
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() {
|
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 {
|
const errorData = useAlertStore((state) => state.errorData);
|
||||||
errorData,
|
const errorOpen = useAlertStore((state) => state.errorOpen);
|
||||||
errorOpen,
|
const setErrorOpen = useAlertStore((state) => state.setErrorOpen);
|
||||||
setErrorOpen,
|
const noticeData = useAlertStore((state) => state.noticeData);
|
||||||
noticeData,
|
const noticeOpen = useAlertStore((state) => state.noticeOpen);
|
||||||
noticeOpen,
|
const setNoticeOpen = useAlertStore((state) => state.setNoticeOpen);
|
||||||
setNoticeOpen,
|
const successData = useAlertStore((state) => state.successData);
|
||||||
successData,
|
const successOpen = useAlertStore((state) => state.successOpen);
|
||||||
successOpen,
|
const setSuccessOpen = useAlertStore((state) => state.setSuccessOpen);
|
||||||
setSuccessOpen,
|
const loading = useAlertStore((state) => state.loading);
|
||||||
loading,
|
const [fetchError, setFetchError] = useState(false);
|
||||||
} = useContext(alertContext);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { fetchError } = useContext(typesContext);
|
|
||||||
|
|
||||||
// Initialize state variable for the list of alerts
|
// Initialize state variable for the list of alerts
|
||||||
const [alertsList, setAlertsList] = useState<
|
const [alertsList, setAlertsList] = useState<
|
||||||
|
|
@ -131,28 +120,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);
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
// Clean up the timer on component unmount
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
//need parent component with width and height
|
//need parent component with width and height
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
onReset={() => {
|
onReset={() => {
|
||||||
window.localStorage.removeItem("tabsData");
|
// any reset function
|
||||||
window.localStorage.clear();
|
|
||||||
hardReset();
|
|
||||||
window.location.href = window.location.href;
|
|
||||||
}}
|
}}
|
||||||
FallbackComponent={CrashErrorComponent}
|
FallbackComponent={CrashErrorComponent}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{fetchError ? (
|
||||||
|
<FetchErrorComponent
|
||||||
|
description={FETCH_ERROR_DESCRIPION}
|
||||||
|
message={FETCH_ERROR_MESSAGE}
|
||||||
|
></FetchErrorComponent>
|
||||||
|
) : loading ? (
|
||||||
<div className="loading-page-panel">
|
<div className="loading-page-panel">
|
||||||
{fetchError ? (
|
<LoadingComponent remSize={50} />
|
||||||
<FetchErrorComponent
|
|
||||||
description={FETCH_ERROR_DESCRIPION}
|
|
||||||
message={FETCH_ERROR_MESSAGE}
|
|
||||||
></FetchErrorComponent>
|
|
||||||
) : (
|
|
||||||
<LoadingComponent remSize={50} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import React, {
|
import React, { ReactNode, useEffect, useRef, useState } from "react";
|
||||||
ReactNode,
|
import { Handle, Position } from "reactflow";
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
|
|
||||||
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
||||||
import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
||||||
import DictComponent from "../../../../components/dictComponent";
|
import DictComponent from "../../../../components/dictComponent";
|
||||||
|
|
@ -26,16 +20,15 @@ import {
|
||||||
LANGFLOW_SUPPORTED_TYPES,
|
LANGFLOW_SUPPORTED_TYPES,
|
||||||
TOOLTIP_EMPTY,
|
TOOLTIP_EMPTY,
|
||||||
} from "../../../../constants/constants";
|
} 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 { 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 { APIClassType } from "../../../../types/api";
|
||||||
import { ParameterComponentType } from "../../../../types/components";
|
import { ParameterComponentType } from "../../../../types/components";
|
||||||
import { NodeDataType } from "../../../../types/flow";
|
import { NodeDataType } from "../../../../types/flow";
|
||||||
import {
|
import {
|
||||||
cleanEdges,
|
|
||||||
convertObjToArray,
|
convertObjToArray,
|
||||||
convertValuesToNumbers,
|
convertValuesToNumbers,
|
||||||
hasDuplicateKeys,
|
hasDuplicateKeys,
|
||||||
|
|
@ -68,47 +61,27 @@ export default function ParameterComponent({
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
|
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
|
||||||
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
|
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
|
||||||
const { setErrorData, modalContextOpen } = useContext(alertContext);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
const [position, setPosition] = useState(0);
|
const nodes = useFlowStore((state) => state.nodes);
|
||||||
const { setTabsState, tabId, flows, tabsState, updateFlow } =
|
const edges = useFlowStore((state) => state.edges);
|
||||||
useContext(FlowsContext);
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
|
|
||||||
const [dataRef, setDataRef] = useState(data);
|
const flow = currentFlow?.data?.nodes ?? null;
|
||||||
|
|
||||||
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 groupedEdge = useRef(null);
|
const groupedEdge = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const setFilterEdge = useTypesStore((state) => state.setFilterEdge);
|
||||||
setDataRef(data);
|
|
||||||
}, [modalContextOpen]);
|
|
||||||
|
|
||||||
const { reactFlowInstance, setFilterEdge } = useContext(typesContext);
|
|
||||||
let disabled =
|
let disabled =
|
||||||
reactFlowInstance
|
edges.some(
|
||||||
?.getEdges()
|
(edge) =>
|
||||||
.some(
|
edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id)
|
||||||
(edge) =>
|
) ?? false;
|
||||||
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 handleUpdateValues = async (name: string, data: NodeDataType) => {
|
||||||
const code = data.node?.template["code"]?.value;
|
const code = data.node?.template["code"]?.value;
|
||||||
|
|
@ -133,68 +106,46 @@ export default function ParameterComponent({
|
||||||
if (data.node!.template[name].value !== newValue) {
|
if (data.node!.template[name].value !== newValue) {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
}
|
}
|
||||||
data.node!.template[name].value = newValue;
|
|
||||||
updateNodeInternals(data.id);
|
|
||||||
|
|
||||||
setDataRef((old) => {
|
data.node!.template[name].value = newValue; // necessary to enable ctrl+z inside the input
|
||||||
let newData = cloneDeep(old);
|
|
||||||
newData.node!.template[name].value = newValue;
|
setNode(data.id, (oldNode) => {
|
||||||
return newData;
|
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();
|
renderTooltips();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNodeClass = (newNodeClass: APIClassType, code?: string): void => {
|
const handleNodeClass = (newNodeClass: APIClassType, code?: string): void => {
|
||||||
if (!data.node) return;
|
if (!data.node) return;
|
||||||
if (data.node!.template[name].value !== newNodeClass.template[name].value) {
|
if (data.node!.template[name].value !== code) {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
}
|
}
|
||||||
data.node! = {
|
|
||||||
...newNodeClass,
|
setNode(data.id, (oldNode) => {
|
||||||
description: newNodeClass.description ?? data.node!.description,
|
let newNode = cloneDeep(oldNode);
|
||||||
display_name: newNodeClass.display_name ?? data.node!.display_name,
|
|
||||||
};
|
newNode.data = {
|
||||||
data.node!.template[name].value = code;
|
...newNode.data,
|
||||||
updateNodeInternals(data.id);
|
node: newNodeClass,
|
||||||
// Set state to pending
|
description: newNodeClass.description ?? data.node!.description,
|
||||||
//@ts-ignore
|
display_name: newNodeClass.display_name ?? data.node!.display_name,
|
||||||
if (data.node!.template[name].value !== code) {
|
};
|
||||||
const tabs = cloneDeep(tabsState);
|
|
||||||
tabs[tabId].isPending = false;
|
newNode.data.node.template[name].value = code;
|
||||||
tabs[tabId].formKeysData = tabsState[tabId].formKeysData;
|
|
||||||
setTabsState({
|
return newNode;
|
||||||
...tabs,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
renderTooltips();
|
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);
|
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||||
|
|
@ -323,7 +274,7 @@ export default function ParameterComponent({
|
||||||
: scapedJSONStringfy(id)
|
: scapedJSONStringfy(id)
|
||||||
}
|
}
|
||||||
isValidConnection={(connection) =>
|
isValidConnection={(connection) =>
|
||||||
isValidConnection(connection, reactFlowInstance!)
|
isValidConnection(connection, nodes, edges)
|
||||||
}
|
}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
|
left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
|
||||||
|
|
@ -331,7 +282,6 @@ export default function ParameterComponent({
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
borderColor: color,
|
borderColor: color,
|
||||||
top: position,
|
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFilterEdge(groupedEdge.current);
|
setFilterEdge(groupedEdge.current);
|
||||||
|
|
@ -344,7 +294,7 @@ export default function ParameterComponent({
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
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
|
<div
|
||||||
|
|
@ -396,7 +346,7 @@ export default function ParameterComponent({
|
||||||
: scapedJSONStringfy(id)
|
: scapedJSONStringfy(id)
|
||||||
}
|
}
|
||||||
isValidConnection={(connection) =>
|
isValidConnection={(connection) =>
|
||||||
isValidConnection(connection, reactFlowInstance!)
|
isValidConnection(connection, nodes, edges)
|
||||||
}
|
}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
left ? "-ml-0.5 " : "-mr-0.5 ",
|
left ? "-ml-0.5 " : "-mr-0.5 ",
|
||||||
|
|
@ -404,7 +354,6 @@ export default function ParameterComponent({
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
borderColor: color,
|
borderColor: color,
|
||||||
top: position,
|
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFilterEdge(groupedEdge.current);
|
setFilterEdge(groupedEdge.current);
|
||||||
|
|
@ -454,9 +403,7 @@ export default function ParameterComponent({
|
||||||
id={"toggle-" + index}
|
id={"toggle-" + index}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
enabled={data.node?.template[name].value ?? false}
|
enabled={data.node?.template[name].value ?? false}
|
||||||
setEnabled={(isEnabled) => {
|
setEnabled={handleOnNewValue}
|
||||||
handleOnNewValue(isEnabled);
|
|
||||||
}}
|
|
||||||
size="large"
|
size="large"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -558,10 +505,7 @@ export default function ParameterComponent({
|
||||||
}
|
}
|
||||||
: data.node!.template[name].value
|
: data.node!.template[name].value
|
||||||
}
|
}
|
||||||
onChange={(newValue) => {
|
onChange={handleOnNewValue}
|
||||||
data.node!.template[name].value = newValue;
|
|
||||||
handleOnNewValue(newValue);
|
|
||||||
}}
|
|
||||||
id="div-dict-input"
|
id="div-dict-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -571,15 +515,14 @@ export default function ParameterComponent({
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
editNode={false}
|
editNode={false}
|
||||||
value={
|
value={
|
||||||
dataRef.node!.template[name].value?.length === 0 ||
|
data.node!.template[name].value?.length === 0 ||
|
||||||
!dataRef.node!.template[name].value
|
!data.node!.template[name].value
|
||||||
? [{ "": "" }]
|
? [{ "": "" }]
|
||||||
: convertObjToArray(dataRef.node!.template[name].value)
|
: convertObjToArray(data.node!.template[name].value)
|
||||||
}
|
}
|
||||||
duplicateKey={errorDuplicateKey}
|
duplicateKey={errorDuplicateKey}
|
||||||
onChange={(newValue) => {
|
onChange={(newValue) => {
|
||||||
const valueToNumbers = convertValuesToNumbers(newValue);
|
const valueToNumbers = convertValuesToNumbers(newValue);
|
||||||
data.node!.template[name].value = valueToNumbers;
|
|
||||||
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
|
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
|
||||||
handleOnNewValue(valueToNumbers);
|
handleOnNewValue(valueToNumbers);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
|
import { NodeToolbar } from "reactflow";
|
||||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||||
import Tooltip from "../../components/TooltipComponent";
|
import Tooltip from "../../components/TooltipComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import InputComponent from "../../components/inputComponent";
|
import InputComponent from "../../components/inputComponent";
|
||||||
import { Textarea } from "../../components/ui/textarea";
|
import { Textarea } from "../../components/ui/textarea";
|
||||||
import { priorityFields } from "../../constants/constants";
|
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 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 { validationStatusType } from "../../types/components";
|
||||||
import { NodeDataType } from "../../types/flow";
|
import { NodeDataType } from "../../types/flow";
|
||||||
import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
|
import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
|
||||||
|
|
@ -30,11 +28,9 @@ export default function GenericNode({
|
||||||
xPos: number;
|
xPos: number;
|
||||||
yPos: number;
|
yPos: number;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { updateFlow, flows, tabId, saveCurrentFlow } =
|
const types = useTypesStore((state) => state.types);
|
||||||
useContext(FlowsContext);
|
const deleteNode = useFlowStore((state) => state.deleteNode);
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
const { types, deleteNode, reactFlowInstance, setFilterEdge, getFilterEdge } =
|
|
||||||
useContext(typesContext);
|
|
||||||
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
|
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
|
||||||
const [inputName, setInputName] = useState(false);
|
const [inputName, setInputName] = useState(false);
|
||||||
const [nodeName, setNodeName] = useState(data.node!.display_name);
|
const [nodeName, setNodeName] = useState(data.node!.display_name);
|
||||||
|
|
@ -46,9 +42,8 @@ export default function GenericNode({
|
||||||
useState<validationStatusType | null>(null);
|
useState<validationStatusType | null>(null);
|
||||||
const [handles, setHandles] = useState<boolean[] | []>([]);
|
const [handles, setHandles] = useState<boolean[] | []>([]);
|
||||||
let numberOfInputs: boolean[] = [];
|
let numberOfInputs: boolean[] = [];
|
||||||
const { modalContextOpen } = useContext(alertContext);
|
|
||||||
|
|
||||||
const { takeSnapshot } = useContext(undoRedoContext);
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
|
|
||||||
function countHandles(): void {
|
function countHandles(): void {
|
||||||
numberOfInputs = Object.keys(data.node!.template)
|
numberOfInputs = Object.keys(data.node!.template)
|
||||||
|
|
@ -84,7 +79,8 @@ export default function GenericNode({
|
||||||
}, [data, data.node]);
|
}, [data, data.node]);
|
||||||
|
|
||||||
// State for outline color
|
// State for outline color
|
||||||
const { sseData, isBuilding } = useSSE();
|
const sseData = useFlowStore((state) => state.sseData);
|
||||||
|
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNodeDescription(data.node!.description);
|
setNodeDescription(data.node!.description);
|
||||||
|
|
@ -118,10 +114,12 @@ export default function GenericNode({
|
||||||
deleteNode={(id) => {
|
deleteNode={(id) => {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
deleteNode(id);
|
deleteNode(id);
|
||||||
saveCurrentFlow();
|
|
||||||
}}
|
}}
|
||||||
setShowNode={(show: boolean) => {
|
setShowNode={(show: boolean) => {
|
||||||
data.showNode = show;
|
setNode(data.id, (old) => ({
|
||||||
|
...old,
|
||||||
|
data: { ...old.data, showNode: show },
|
||||||
|
}));
|
||||||
}}
|
}}
|
||||||
numberOfHandles={handles}
|
numberOfHandles={handles}
|
||||||
showNode={showNode}
|
showNode={showNode}
|
||||||
|
|
@ -173,8 +171,16 @@ export default function GenericNode({
|
||||||
setInputName(false);
|
setInputName(false);
|
||||||
if (nodeName.trim() !== "") {
|
if (nodeName.trim() !== "") {
|
||||||
setNodeName(nodeName);
|
setNodeName(nodeName);
|
||||||
data.node!.display_name = nodeName;
|
setNode(data.id, (old) => ({
|
||||||
updateNodeInternals(data.id);
|
...old,
|
||||||
|
data: {
|
||||||
|
...old.data,
|
||||||
|
node: {
|
||||||
|
...old.data.node,
|
||||||
|
display_name: nodeName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
setNodeName(data.node!.display_name);
|
setNodeName(data.node!.display_name);
|
||||||
}
|
}
|
||||||
|
|
@ -380,8 +386,16 @@ export default function GenericNode({
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
setInputDescription(false);
|
setInputDescription(false);
|
||||||
setNodeDescription(nodeDescription);
|
setNodeDescription(nodeDescription);
|
||||||
data.node!.description = nodeDescription;
|
setNode(data.id, (old) => ({
|
||||||
updateNodeInternals(data.id);
|
...old,
|
||||||
|
data: {
|
||||||
|
...old.data,
|
||||||
|
node: {
|
||||||
|
...old.data.node,
|
||||||
|
description: nodeDescription,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
}}
|
}}
|
||||||
value={nodeDescription}
|
value={nodeDescription}
|
||||||
onChange={(e) => setNodeDescription(e.target.value)}
|
onChange={(e) => setNodeDescription(e.target.value)}
|
||||||
|
|
@ -395,8 +409,16 @@ export default function GenericNode({
|
||||||
) {
|
) {
|
||||||
setInputDescription(false);
|
setInputDescription(false);
|
||||||
setNodeDescription(nodeDescription);
|
setNodeDescription(nodeDescription);
|
||||||
data.node!.description = nodeDescription;
|
setNode(data.id, (old) => ({
|
||||||
updateNodeInternals(data.id);
|
...old,
|
||||||
|
data: {
|
||||||
|
...old.data,
|
||||||
|
node: {
|
||||||
|
...old.data.node,
|
||||||
|
description: nodeDescription,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,27 @@
|
||||||
import { useContext, useState } from "react";
|
import { useState } from "react";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "../../components/ui/popover";
|
} from "../../components/ui/popover";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import { AlertDropdownType } from "../../types/alerts";
|
import { AlertDropdownType } from "../../types/alerts";
|
||||||
import SingleAlert from "./components/singleAlertComponent";
|
import SingleAlert from "./components/singleAlertComponent";
|
||||||
|
|
||||||
export default function AlertDropdown({
|
export default function AlertDropdown({
|
||||||
children,
|
children,
|
||||||
}: AlertDropdownType): JSX.Element {
|
}: AlertDropdownType): JSX.Element {
|
||||||
const {
|
const notificationList = useAlertStore((state) => state.notificationList);
|
||||||
notificationList,
|
const clearNotificationList = useAlertStore(
|
||||||
clearNotificationList,
|
(state) => state.clearNotificationList
|
||||||
removeFromNotificationList,
|
);
|
||||||
setNotificationCenter,
|
const removeFromNotificationList = useAlertStore(
|
||||||
} = useContext(alertContext);
|
(state) => state.removeFromNotificationList
|
||||||
|
);
|
||||||
|
const setNotificationCenter = useAlertStore(
|
||||||
|
(state) => state.setNotificationCenter
|
||||||
|
);
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,16 @@
|
||||||
import { useContext, useEffect } from "react";
|
import { useContext } from "react";
|
||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
|
||||||
export const ProtectedAdminRoute = ({ children }) => {
|
export const ProtectedAdminRoute = ({ children }) => {
|
||||||
const {
|
const { isAdmin, isAuthenticated, logout, userData, autoLogin } =
|
||||||
isAdmin,
|
useContext(AuthContext);
|
||||||
isAuthenticated,
|
|
||||||
logout,
|
|
||||||
getAuthentication,
|
|
||||||
userData,
|
|
||||||
autoLogin,
|
|
||||||
} = useContext(AuthContext);
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isAuthenticated && !getAuthentication()) {
|
|
||||||
window.location.replace("/login");
|
|
||||||
logout();
|
|
||||||
}
|
|
||||||
}, [isAuthenticated, getAuthentication, logout, userData]);
|
|
||||||
|
|
||||||
if (!isAuthenticated && !getAuthentication()) {
|
if (!isAuthenticated) {
|
||||||
return <Navigate to="/login" replace />;
|
logout();
|
||||||
}
|
} else if ((userData && !isAdmin) || autoLogin) {
|
||||||
|
|
||||||
if ((userData && !isAdmin) || autoLogin) {
|
|
||||||
return <Navigate to="/" replace />;
|
return <Navigate to="/" replace />;
|
||||||
|
} else {
|
||||||
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
return children;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { Navigate } from "react-router-dom";
|
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
|
||||||
export const ProtectedRoute = ({ children }) => {
|
export const ProtectedRoute = ({ children }) => {
|
||||||
const { isAuthenticated, logout, getAuthentication } =
|
const { isAuthenticated, logout } = useContext(AuthContext);
|
||||||
useContext(AuthContext);
|
if (!isAuthenticated) {
|
||||||
if (!isAuthenticated && !getAuthentication()) {
|
|
||||||
logout();
|
logout();
|
||||||
return <Navigate to="/login" replace />;
|
} else {
|
||||||
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
return children;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@ import { Navigate } from "react-router-dom";
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
|
||||||
export const ProtectedLoginRoute = ({ children }) => {
|
export const ProtectedLoginRoute = ({ children }) => {
|
||||||
const { getAuthentication, autoLogin } = useContext(AuthContext);
|
const { isAuthenticated, autoLogin } = useContext(AuthContext);
|
||||||
|
|
||||||
if (autoLogin === true) {
|
if (autoLogin === true) {
|
||||||
window.location.replace("/");
|
window.location.replace("/");
|
||||||
return <Navigate to="/" replace />;
|
return <Navigate to="/" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getAuthentication()) {
|
if (isAuthenticated) {
|
||||||
window.location.replace("/");
|
window.location.replace("/");
|
||||||
return <Navigate to="/" replace />;
|
return <Navigate to="/" replace />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
|
||||||
import { StoreContext } from "../../contexts/storeContext";
|
|
||||||
import { getComponent, postLikeComponent } from "../../controllers/API";
|
import { getComponent, postLikeComponent } from "../../controllers/API";
|
||||||
import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal";
|
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 { storeComponent } from "../../types/store";
|
||||||
import cloneFLowWithParent from "../../utils/storeUtils";
|
import cloneFLowWithParent from "../../utils/storeUtils";
|
||||||
import { cn } from "../../utils/utils";
|
import { cn } from "../../utils/utils";
|
||||||
|
|
@ -32,9 +32,10 @@ export default function CollectionCardComponent({
|
||||||
button?: JSX.Element;
|
button?: JSX.Element;
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { addFlow } = useContext(FlowsContext);
|
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||||
const { setSuccessData, setErrorData } = useContext(alertContext);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const { setValidApiKey } = useContext(StoreContext);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
|
||||||
const isStore = false;
|
const isStore = false;
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [loadingLike, setLoadingLike] = useState(false);
|
const [loadingLike, setLoadingLike] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,12 @@
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { useContext, useState } from "react";
|
import { useState } from "react";
|
||||||
import Loading from "../../../components/ui/loading";
|
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 { postBuildInit } from "../../../controllers/API";
|
||||||
import { FlowType } from "../../../types/flow";
|
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 { parsedDataType } from "../../../types/components";
|
||||||
import { FlowsState } from "../../../types/tabs";
|
|
||||||
import { validateNodes } from "../../../utils/reactflowUtils";
|
import { validateNodes } from "../../../utils/reactflowUtils";
|
||||||
import RadialProgressComponent from "../../RadialProgress";
|
import RadialProgressComponent from "../../RadialProgress";
|
||||||
import IconComponent from "../../genericIconComponent";
|
import IconComponent from "../../genericIconComponent";
|
||||||
|
|
@ -24,11 +21,15 @@ export default function BuildTrigger({
|
||||||
setIsBuilt: any;
|
setIsBuilt: any;
|
||||||
isBuilt: boolean;
|
isBuilt: boolean;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE();
|
const updateSSEData = useFlowStore((state) => state.updateSSEData);
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||||
const { setTabsState, saveFlow } = useContext(FlowsContext);
|
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
|
||||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
const nodes = useFlowStore((state) => state.nodes);
|
||||||
const [isIconTouched, setIsIconTouched] = useState(false);
|
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 eventClick = isBuilding ? "pointer-events-none" : "";
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
|
|
@ -37,10 +38,7 @@ export default function BuildTrigger({
|
||||||
if (isBuilding) {
|
if (isBuilding) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const errors = validateNodes(
|
const errors = validateNodes(nodes, edges);
|
||||||
reactFlowInstance!.getNodes(),
|
|
||||||
reactFlowInstance!.getEdges()
|
|
||||||
);
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "Oops! Looks like you missed something",
|
title: "Oops! Looks like you missed something",
|
||||||
|
|
@ -76,7 +74,6 @@ export default function BuildTrigger({
|
||||||
}
|
}
|
||||||
async function streamNodeData(flow: FlowType) {
|
async function streamNodeData(flow: FlowType) {
|
||||||
// Step 1: Make a POST request to send the flow data and receive a unique session ID
|
// 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 response = await postBuildInit(flow);
|
||||||
const { flowId } = response.data;
|
const { flowId } = response.data;
|
||||||
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
// 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
|
// If the event is a log, log it
|
||||||
setSuccessData({ title: parsedData.log });
|
setSuccessData({ title: parsedData.log });
|
||||||
} else if (parsedData.input_keys !== undefined) {
|
} else if (parsedData.input_keys !== undefined) {
|
||||||
//@ts-ignore
|
setFlowState(parsedData);
|
||||||
setTabsState((old: FlowsState) => {
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
[flowId]: {
|
|
||||||
...old[flowId],
|
|
||||||
formKeysData: parsedData,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, process the data
|
// Otherwise, process the data
|
||||||
const isValid = processStreamResult(parsedData);
|
const isValid = processStreamResult(parsedData);
|
||||||
|
|
@ -154,14 +142,6 @@ export default function BuildTrigger({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
setIsIconTouched(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
setIsIconTouched(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition
|
<Transition
|
||||||
show={!open}
|
show={!open}
|
||||||
|
|
@ -179,8 +159,6 @@ export default function BuildTrigger({
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleBuild(flow);
|
handleBuild(flow);
|
||||||
}}
|
}}
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
>
|
||||||
<button>
|
<button>
|
||||||
<div className="round-button-div">
|
<div className="round-button-div">
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
|
|
||||||
import { useContext } from "react";
|
|
||||||
import {
|
import {
|
||||||
CHAT_CANNOT_OPEN_DESCRIPTION,
|
CHAT_CANNOT_OPEN_DESCRIPTION,
|
||||||
CHAT_CANNOT_OPEN_TITLE,
|
CHAT_CANNOT_OPEN_TITLE,
|
||||||
FLOW_NOT_BUILT_DESCRIPTION,
|
FLOW_NOT_BUILT_DESCRIPTION,
|
||||||
FLOW_NOT_BUILT_TITLE,
|
FLOW_NOT_BUILT_TITLE,
|
||||||
} from "../../../constants/constants";
|
} from "../../../constants/constants";
|
||||||
import { alertContext } from "../../../contexts/alertContext";
|
import useAlertStore from "../../../stores/alertStore";
|
||||||
import { chatTriggerPropType } from "../../../types/components";
|
import { chatTriggerPropType } from "../../../types/components";
|
||||||
import IconComponent from "../../genericIconComponent";
|
import IconComponent from "../../genericIconComponent";
|
||||||
|
|
||||||
|
|
@ -17,7 +16,7 @@ export default function ChatTrigger({
|
||||||
isBuilt,
|
isBuilt,
|
||||||
canOpen,
|
canOpen,
|
||||||
}: chatTriggerPropType): JSX.Element {
|
}: chatTriggerPropType): JSX.Element {
|
||||||
const { setErrorData } = useContext(alertContext);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
|
||||||
function handleClick(): void {
|
function handleClick(): void {
|
||||||
if (isBuilt) {
|
if (isBuilt) {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useNodes } from "reactflow";
|
import { useNodes } from "reactflow";
|
||||||
import { ChatType } from "../../types/chat";
|
import { ChatType } from "../../types/chat";
|
||||||
import BuildTrigger from "./buildTrigger";
|
import BuildTrigger from "./buildTrigger";
|
||||||
import ChatTrigger from "./chatTrigger";
|
import ChatTrigger from "./chatTrigger";
|
||||||
|
|
||||||
import * as _ from "lodash";
|
import * as _ from "lodash";
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
|
||||||
import { getBuildStatus } from "../../controllers/API";
|
import { getBuildStatus } from "../../controllers/API";
|
||||||
import FormModal from "../../modals/formModal";
|
import FormModal from "../../modals/formModal";
|
||||||
|
import useFlowStore from "../../stores/flowStore";
|
||||||
import { NodeType } from "../../types/flow";
|
import { NodeType } from "../../types/flow";
|
||||||
|
|
||||||
export default function Chat({ flow }: ChatType): JSX.Element {
|
export default function Chat({ flow }: ChatType): JSX.Element {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [canOpen, setCanOpen] = useState(false);
|
const isBuilt = useFlowStore((state) => state.isBuilt);
|
||||||
const { tabsState, isBuilt, setIsBuilt } = useContext(FlowsContext);
|
const setIsBuilt = useFlowStore((state) => state.setIsBuilt);
|
||||||
|
const flowState = useFlowStore((state) => state.flowState);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (
|
if (
|
||||||
|
|
@ -50,27 +50,11 @@ export default function Chat({ flow }: ChatType): JSX.Element {
|
||||||
const currentNodes = nodes.map((node: NodeType) =>
|
const currentNodes = nodes.map((node: NodeType) =>
|
||||||
_.cloneDeep(node.data.node?.template)
|
_.cloneDeep(node.data.node?.template)
|
||||||
);
|
);
|
||||||
if (
|
if (JSON.stringify(prevNodes) !== JSON.stringify(currentNodes)) {
|
||||||
tabsState &&
|
|
||||||
tabsState[flow.id] &&
|
|
||||||
tabsState[flow.id].isPending &&
|
|
||||||
JSON.stringify(prevNodes) !== JSON.stringify(currentNodes)
|
|
||||||
) {
|
|
||||||
setIsBuilt(false);
|
setIsBuilt(false);
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
tabsState &&
|
|
||||||
tabsState[flow.id] &&
|
|
||||||
tabsState[flow.id].formKeysData &&
|
|
||||||
tabsState[flow.id].formKeysData.input_keys !== null
|
|
||||||
) {
|
|
||||||
setCanOpen(true);
|
|
||||||
} else {
|
|
||||||
setCanOpen(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
prevNodesRef.current = currentNodes;
|
prevNodesRef.current = currentNodes;
|
||||||
}, [tabsState, flow.id]);
|
}, [flowState, flow.id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -81,19 +65,11 @@ export default function Chat({ flow }: ChatType): JSX.Element {
|
||||||
setIsBuilt={setIsBuilt}
|
setIsBuilt={setIsBuilt}
|
||||||
isBuilt={isBuilt}
|
isBuilt={isBuilt}
|
||||||
/>
|
/>
|
||||||
{isBuilt &&
|
{isBuilt && flowState && !!flowState?.input_keys && (
|
||||||
tabsState[flow.id] &&
|
<FormModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
|
||||||
tabsState[flow.id].formKeysData &&
|
)}
|
||||||
canOpen && (
|
|
||||||
<FormModal
|
|
||||||
key={flow.id}
|
|
||||||
flow={flow}
|
|
||||||
open={open}
|
|
||||||
setOpen={setOpen}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<ChatTrigger
|
<ChatTrigger
|
||||||
canOpen={canOpen}
|
canOpen={!!flowState?.input_keys}
|
||||||
open={open}
|
open={open}
|
||||||
setOpen={setOpen}
|
setOpen={setOpen}
|
||||||
isBuilt={isBuilt}
|
isBuilt={isBuilt}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,11 @@ export default function CodeAreaComponent({
|
||||||
typeof value == "string" ? value : JSON.stringify(value)
|
typeof value == "string" ? value : JSON.stringify(value)
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled && myValue !== "") {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMyValue(typeof value == "string" ? value : JSON.stringify(value));
|
setMyValue(typeof value == "string" ? value : JSON.stringify(value));
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||||
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||||
import AccordionComponent from "../../components/AccordionComponent";
|
import AccordionComponent from "../../components/AccordionComponent";
|
||||||
|
|
@ -28,8 +28,8 @@ import {
|
||||||
TabsTrigger,
|
TabsTrigger,
|
||||||
} from "../../components/ui/tabs";
|
} from "../../components/ui/tabs";
|
||||||
import { LANGFLOW_SUPPORTED_TYPES } from "../../constants/constants";
|
import { LANGFLOW_SUPPORTED_TYPES } from "../../constants/constants";
|
||||||
import { darkContext } from "../../contexts/darkContext";
|
import { useDarkStore } from "../../stores/darkStore";
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
import { codeTabsPropsType } from "../../types/components";
|
import { codeTabsPropsType } from "../../types/components";
|
||||||
import {
|
import {
|
||||||
convertObjToArray,
|
convertObjToArray,
|
||||||
|
|
@ -53,8 +53,10 @@ export default function CodeTabsComponent({
|
||||||
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
||||||
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
|
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
|
||||||
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
|
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
|
||||||
const { dark } = useContext(darkContext);
|
const dark = useDarkStore((state) => state.dark);
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
|
||||||
|
const setNodes = useFlowStore((state) => state.setNodes);
|
||||||
|
|
||||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -68,7 +70,7 @@ export default function CodeTabsComponent({
|
||||||
unselectAllNodes({
|
unselectAllNodes({
|
||||||
data,
|
data,
|
||||||
updateNodes: (nodes) => {
|
updateNodes: (nodes) => {
|
||||||
reactFlowInstance?.setNodes(nodes);
|
setNodes(nodes);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@ export default function FloatComponent({
|
||||||
const max = rangeSpec?.max ?? 2;
|
const max = rangeSpec?.max ?? 2;
|
||||||
// Clear component state
|
// Clear component state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled && value !== "") {
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
|
@ -48,7 +48,7 @@ export default function FloatComponent({
|
||||||
onChange(event.target.value);
|
onChange(event.target.value);
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
handleKeyDown(e, value, "0");
|
handleKeyDown(e, value, "");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { useContext, useState } from "react";
|
import { useState } from "react";
|
||||||
import { FlowsContext } from "../../../../contexts/flowsContext";
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
|
|
@ -9,17 +8,18 @@ import {
|
||||||
} from "../../../ui/dropdown-menu";
|
} from "../../../ui/dropdown-menu";
|
||||||
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { alertContext } from "../../../../contexts/alertContext";
|
|
||||||
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
|
|
||||||
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
|
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 IconComponent from "../../../genericIconComponent";
|
||||||
import { Button } from "../../../ui/button";
|
import { Button } from "../../../ui/button";
|
||||||
|
|
||||||
export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
|
export const MenuBar = (): JSX.Element => {
|
||||||
const { addFlow } = useContext(FlowsContext);
|
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||||
const { setErrorData } = useContext(alertContext);
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
const { undo, redo } = useContext(undoRedoContext);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
const undo = useFlowsManagerStore((state) => state.undo);
|
||||||
|
const redo = useFlowsManagerStore((state) => state.redo);
|
||||||
const [openSettings, setOpenSettings] = useState(false);
|
const [openSettings, setOpenSettings] = useState(false);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
@ -34,9 +34,8 @@ export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
|
||||||
setErrorData(err as { title: string; list?: Array<string> });
|
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">
|
<div className="round-button-div">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -50,9 +49,7 @@ export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button asChild variant="primary" size="sm">
|
<Button asChild variant="primary" size="sm">
|
||||||
<div className="header-menu-bar-display">
|
<div className="header-menu-bar-display">
|
||||||
<div className="header-menu-flow-name">
|
<div className="header-menu-flow-name">{currentFlow.name}</div>
|
||||||
{current_flow!.name}
|
|
||||||
</div>
|
|
||||||
<IconComponent name="ChevronDown" className="h-4 w-4" />
|
<IconComponent name="ChevronDown" className="h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -108,6 +105,8 @@ export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
|
||||||
></FlowSettingsModal>
|
></FlowSettingsModal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import { useContext } from "react";
|
import { useContext, useEffect } from "react";
|
||||||
import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
|
import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
|
||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
import AlertDropdown from "../../alerts/alertDropDown";
|
import AlertDropdown from "../../alerts/alertDropDown";
|
||||||
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
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 { gradients } from "../../utils/styleUtils";
|
||||||
import IconComponent from "../genericIconComponent";
|
import IconComponent from "../genericIconComponent";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
|
|
@ -24,25 +23,33 @@ import { Separator } from "../ui/separator";
|
||||||
import MenuBar from "./components/menuBar";
|
import MenuBar from "./components/menuBar";
|
||||||
|
|
||||||
export default function Header(): JSX.Element {
|
export default function Header(): JSX.Element {
|
||||||
const { flows, tabId } = useContext(FlowsContext);
|
const notificationCenter = useAlertStore((state) => state.notificationCenter);
|
||||||
const { dark, setDark } = useContext(darkContext);
|
|
||||||
const { notificationCenter } = useContext(alertContext);
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { logout, autoLogin, isAdmin, userData } = useContext(AuthContext);
|
const { logout, autoLogin, isAdmin, userData } = useContext(AuthContext);
|
||||||
const { hasStore } = useContext(StoreContext);
|
|
||||||
const { stars, gradientIndex } = useContext(darkContext);
|
|
||||||
const navigate = useNavigate();
|
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 (
|
return (
|
||||||
<div className="header-arrangement">
|
<div className="header-arrangement">
|
||||||
<div className="header-start-display lg:w-[30%]">
|
<div className="header-start-display lg:w-[30%]">
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
<span className="ml-4 text-2xl">⛓️</span>
|
<span className="ml-4 text-2xl">⛓️</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
<MenuBar />
|
||||||
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
|
|
||||||
<MenuBar flows={flows} tabId={tabId} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="round-button-div">
|
<div className="round-button-div">
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
|
|
@ -159,7 +166,10 @@ export default function Header(): JSX.Element {
|
||||||
<button
|
<button
|
||||||
className={
|
className={
|
||||||
"h-7 w-7 rounded-full focus-visible:outline-0 " +
|
"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>
|
</DropdownMenuTrigger>
|
||||||
|
|
@ -184,7 +194,6 @@ export default function Header(): JSX.Element {
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
logout();
|
logout();
|
||||||
navigate("/login");
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Sign Out
|
Sign Out
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,10 @@ export default function InputComponent({
|
||||||
const refInput = useRef<HTMLInputElement>(null);
|
const refInput = useRef<HTMLInputElement>(null);
|
||||||
// Clear component state
|
// Clear component state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled && value !== "") {
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
|
|
@ -59,9 +59,6 @@ export default function InputComponent({
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.ctrlKey && e.key === "c") {
|
|
||||||
// Perform any actions you need when Ctrl+C is detected
|
|
||||||
}
|
|
||||||
handleKeyDown(e, value, "");
|
handleKeyDown(e, value, "");
|
||||||
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
|
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
|
||||||
import { uploadFile } from "../../controllers/API";
|
import { uploadFile } from "../../controllers/API";
|
||||||
|
import useAlertStore from "../../stores/alertStore";
|
||||||
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { FileComponentType } from "../../types/components";
|
import { FileComponentType } from "../../types/components";
|
||||||
import IconComponent from "../genericIconComponent";
|
import IconComponent from "../genericIconComponent";
|
||||||
|
|
||||||
|
|
@ -13,14 +13,14 @@ export default function InputFileComponent({
|
||||||
onFileChange,
|
onFileChange,
|
||||||
editNode = false,
|
editNode = false,
|
||||||
}: FileComponentType): JSX.Element {
|
}: FileComponentType): JSX.Element {
|
||||||
|
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||||
const [myValue, setMyValue] = useState(value);
|
const [myValue, setMyValue] = useState(value);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { setErrorData } = useContext(alertContext);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const { tabId } = useContext(FlowsContext);
|
|
||||||
|
|
||||||
// Clear component state
|
// Clear component state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled && value !== "") {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
onChange("");
|
onChange("");
|
||||||
onFileChange("");
|
onFileChange("");
|
||||||
|
|
@ -58,7 +58,7 @@ export default function InputFileComponent({
|
||||||
// Check if the file type is correct
|
// Check if the file type is correct
|
||||||
if (file && checkFileType(file.name)) {
|
if (file && checkFileType(file.name)) {
|
||||||
// Upload the file
|
// Upload the file
|
||||||
uploadFile(file, tabId)
|
uploadFile(file, currentFlowId)
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log("File uploaded successfully");
|
console.log("File uploaded successfully");
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export default function InputListComponent({
|
||||||
editNode = false,
|
editNode = false,
|
||||||
}: InputListComponentType): JSX.Element {
|
}: InputListComponentType): JSX.Element {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled && value.length > 0 && value[0] !== "") {
|
||||||
onChange([""]);
|
onChange([""]);
|
||||||
}
|
}
|
||||||
}, [disabled]);
|
}, [disabled]);
|
||||||
|
|
@ -44,12 +44,6 @@ export default function InputListComponent({
|
||||||
newInputList[idx] = event.target.value;
|
newInputList[idx] = event.target.value;
|
||||||
onChange(newInputList);
|
onChange(newInputList);
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.ctrlKey && e.key === "Backspace") {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{idx === value.length - 1 ? (
|
{idx === value.length - 1 ? (
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { IntComponentType } from "../../types/components";
|
import { IntComponentType } from "../../types/components";
|
||||||
import { handleKeyDown } from "../../utils/reactflowUtils";
|
import {
|
||||||
|
handleKeyDown,
|
||||||
|
handleOnlyIntegerInput,
|
||||||
|
} from "../../utils/reactflowUtils";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
|
|
||||||
export default function IntComponent({
|
export default function IntComponent({
|
||||||
|
|
@ -14,7 +17,7 @@ export default function IntComponent({
|
||||||
|
|
||||||
// Clear component state
|
// Clear component state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled && value !== "") {
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
|
|
@ -24,29 +27,12 @@ export default function IntComponent({
|
||||||
<Input
|
<Input
|
||||||
id={id}
|
id={id}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (
|
handleOnlyIntegerInput(event);
|
||||||
event.key !== "Backspace" &&
|
handleKeyDown(event, value, "");
|
||||||
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");
|
|
||||||
}}
|
}}
|
||||||
type="number"
|
type="number"
|
||||||
step="1"
|
step="1"
|
||||||
min={min}
|
min={0}
|
||||||
onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
|
onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (Number(event.target.value) < min) {
|
if (Number(event.target.value) < min) {
|
||||||
event.target.value = min.toString();
|
event.target.value = min.toString();
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default function KeypairListComponent({
|
||||||
duplicateKey,
|
duplicateKey,
|
||||||
}: KeyPairListComponentType): JSX.Element {
|
}: KeyPairListComponentType): JSX.Element {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled && value.length > 0 && value[0] !== "") {
|
||||||
onChange([""]);
|
onChange([""]);
|
||||||
}
|
}
|
||||||
}, [disabled]);
|
}, [disabled]);
|
||||||
|
|
@ -65,12 +65,6 @@ export default function KeypairListComponent({
|
||||||
)}
|
)}
|
||||||
placeholder="Type key..."
|
placeholder="Type key..."
|
||||||
onChange={(event) => handleChangeKey(event, index)}
|
onChange={(event) => handleChangeKey(event, index)}
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.ctrlKey && e.key === "Backspace") {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export default function PageLayout({
|
||||||
description: string;
|
description: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
button?: React.ReactNode;
|
button?: React.ReactNode;
|
||||||
betaIcon: boolean;
|
betaIcon?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-full flex-col">
|
<div className="flex h-screen w-full flex-col">
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export default function PromptAreaComponent({
|
||||||
readonly = false,
|
readonly = false,
|
||||||
}: PromptAreaComponentType): JSX.Element {
|
}: PromptAreaComponentType): JSX.Element {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled && value !== "") {
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled]);
|
}, [disabled]);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { useContext } from "react";
|
|
||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
import { StoreContext } from "../../contexts/storeContext";
|
import { useStoreStore } from "../../stores/storeStore";
|
||||||
|
|
||||||
export const StoreGuard = ({ children }) => {
|
export const StoreGuard = ({ children }) => {
|
||||||
const { hasStore } = useContext(StoreContext);
|
const hasStore = useStoreStore((state) => state.hasStore);
|
||||||
|
|
||||||
if (!hasStore) {
|
if (!hasStore) {
|
||||||
return <Navigate to="/flows" replace />;
|
return <Navigate to="/flows" replace />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { darkContext } from "../../contexts/darkContext";
|
|
||||||
import { cn } from "../../utils/utils";
|
import { cn } from "../../utils/utils";
|
||||||
import { Badge } from "../ui/badge";
|
import { Badge } from "../ui/badge";
|
||||||
|
|
||||||
|
|
@ -24,7 +23,6 @@ export function TagsSelector({
|
||||||
: selectedTags.filter((_, i) => i !== index);
|
: selectedTags.filter((_, i) => i !== index);
|
||||||
setSelectedTags(newArray);
|
setSelectedTags(newArray);
|
||||||
};
|
};
|
||||||
const { dark } = useContext(darkContext);
|
|
||||||
|
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const fadeContainerRef = useRef<HTMLDivElement>(null);
|
const fadeContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default function TextAreaComponent({
|
||||||
}: TextAreaComponentType): JSX.Element {
|
}: TextAreaComponentType): JSX.Element {
|
||||||
// Clear text area
|
// Clear text area
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled && value !== "") {
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled]);
|
}, [disabled]);
|
||||||
|
|
|
||||||
|
|
@ -24,16 +24,16 @@ const AccordionTrigger = React.forwardRef<
|
||||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||||
>(({ className, children, ...props }, ref) => (
|
>(({ className, children, ...props }, ref) => (
|
||||||
<AccordionPrimitive.Header className="flex">
|
<AccordionPrimitive.Header className="flex">
|
||||||
<AccordionPrimitive.Trigger
|
<AccordionPrimitive.Trigger asChild ref={ref} {...props}>
|
||||||
ref={ref}
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180",
|
"flex flex-1 cursor-pointer items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
>
|
||||||
>
|
{children}
|
||||||
{children}
|
<ChevronDownIcon className="h-4 w-4 text-muted-foreground transition-transform duration-200" />
|
||||||
<ChevronDownIcon className="h-4 w-4 text-muted-foreground transition-transform duration-200" />
|
</div>
|
||||||
</AccordionPrimitive.Trigger>
|
</AccordionPrimitive.Trigger>
|
||||||
</AccordionPrimitive.Header>
|
</AccordionPrimitive.Header>
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,26 @@ const Checkbox = React.forwardRef<
|
||||||
));
|
));
|
||||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
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 };
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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 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 { Users } from "../types/api";
|
||||||
import { AuthContextType } from "../types/contexts/auth";
|
import { AuthContextType } from "../types/contexts/auth";
|
||||||
import { alertContext } from "./alertContext";
|
|
||||||
|
|
||||||
const initialValue: AuthContextType = {
|
const initialValue: AuthContextType = {
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
setIsAdmin: () => false,
|
setIsAdmin: () => false,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
refreshToken: null,
|
|
||||||
login: () => {},
|
login: () => {},
|
||||||
logout: () => {},
|
logout: () => new Promise(() => {}),
|
||||||
userData: null,
|
userData: null,
|
||||||
setUserData: () => {},
|
setUserData: () => {},
|
||||||
getAuthentication: () => false,
|
|
||||||
authenticationErrorCount: 0,
|
authenticationErrorCount: 0,
|
||||||
autoLogin: false,
|
autoLogin: false,
|
||||||
setAutoLogin: () => {},
|
setAutoLogin: () => {},
|
||||||
|
|
@ -27,24 +30,24 @@ const initialValue: AuthContextType = {
|
||||||
export const AuthContext = createContext<AuthContextType>(initialValue);
|
export const AuthContext = createContext<AuthContextType>(initialValue);
|
||||||
|
|
||||||
export function AuthProvider({ children }): React.ReactElement {
|
export function AuthProvider({ children }): React.ReactElement {
|
||||||
|
const navigate = useNavigate();
|
||||||
const cookies = new Cookies();
|
const cookies = new Cookies();
|
||||||
const [accessToken, setAccessToken] = useState<string | null>(
|
const [accessToken, setAccessToken] = useState<string | null>(
|
||||||
cookies.get("access_tkn_lflw")
|
cookies.get("access_token_lf") ?? null
|
||||||
);
|
);
|
||||||
const [refreshToken, setRefreshToken] = useState<string | null>(
|
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(
|
||||||
cookies.get("refresh_tkn_lflw")
|
!!cookies.get("access_token_lf")
|
||||||
);
|
);
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
|
||||||
const [isAdmin, setIsAdmin] = useState<boolean>(false);
|
const [isAdmin, setIsAdmin] = useState<boolean>(false);
|
||||||
const [userData, setUserData] = useState<Users | null>(null);
|
const [userData, setUserData] = useState<Users | null>(null);
|
||||||
const [autoLogin, setAutoLogin] = useState<boolean>(false);
|
const [autoLogin, setAutoLogin] = useState<boolean>(false);
|
||||||
const { setLoading } = useContext(alertContext);
|
const setLoading = useAlertStore((state) => state.setLoading);
|
||||||
const [apiKey, setApiKey] = useState<string | null>(
|
const [apiKey, setApiKey] = useState<string | null>(
|
||||||
cookies.get("apikey_tkn_lflw")
|
cookies.get("apikey_tkn_lflw")
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedAccessToken = cookies.get("access_tkn_lflw");
|
const storedAccessToken = cookies.get("access_token_lf");
|
||||||
if (storedAccessToken) {
|
if (storedAccessToken) {
|
||||||
setAccessToken(storedAccessToken);
|
setAccessToken(storedAccessToken);
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +67,7 @@ export function AuthProvider({ children }): React.ReactElement {
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
if (user && user["access_token"]) {
|
if (user && user["access_token"]) {
|
||||||
user["refresh_token"] = "auto";
|
user["refresh_token"] = "auto";
|
||||||
login(user["access_token"], user["refresh_token"]);
|
login(user["access_token"]);
|
||||||
setUserData(user);
|
setUserData(user);
|
||||||
setAutoLogin(true);
|
setAutoLogin(true);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
@ -72,48 +75,47 @@ export function AuthProvider({ children }): React.ReactElement {
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
setAutoLogin(false);
|
setAutoLogin(false);
|
||||||
if (getAuthentication() && !isLoginPage) {
|
if (isAuthenticated && !isLoginPage) {
|
||||||
getLoggedUser()
|
getUser();
|
||||||
.then((user) => {
|
|
||||||
setUserData(user);
|
|
||||||
setLoading(false);
|
|
||||||
const isSuperUser = user!.is_superuser;
|
|
||||||
setIsAdmin(isSuperUser);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log("auth context");
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [setUserData, setLoading, autoLogin, setIsAdmin]);
|
}, [setUserData, setLoading, autoLogin, setIsAdmin]);
|
||||||
|
|
||||||
function getAuthentication() {
|
function getUser() {
|
||||||
const storedRefreshToken = cookies.get("refresh_tkn_lflw");
|
getLoggedUser()
|
||||||
const storedAccess = cookies.get("access_tkn_lflw");
|
.then((user) => {
|
||||||
const auth = storedAccess && storedRefreshToken ? true : false;
|
setUserData(user);
|
||||||
return auth;
|
setLoading(false);
|
||||||
|
const isSuperUser = user!.is_superuser;
|
||||||
|
setIsAdmin(isSuperUser);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("auth context");
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function login(newAccessToken: string, refreshToken: string) {
|
function login(newAccessToken: string) {
|
||||||
cookies.set("access_tkn_lflw", newAccessToken, { path: "/" });
|
|
||||||
cookies.set("refresh_tkn_lflw", refreshToken, { path: "/" });
|
|
||||||
setAccessToken(newAccessToken);
|
setAccessToken(newAccessToken);
|
||||||
setRefreshToken(refreshToken);
|
|
||||||
setIsAuthenticated(true);
|
setIsAuthenticated(true);
|
||||||
|
getUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
async function logout() {
|
||||||
cookies.remove("access_tkn_lflw", { path: "/" });
|
try {
|
||||||
cookies.remove("refresh_tkn_lflw", { path: "/" });
|
await requestLogout();
|
||||||
cookies.remove("apikey_tkn_lflw", { path: "/" });
|
cookies.remove("apikey_tkn_lflw", { path: "/" });
|
||||||
setIsAdmin(false);
|
setIsAdmin(false);
|
||||||
setUserData(null);
|
setUserData(null);
|
||||||
setAccessToken(null);
|
setAccessToken(null);
|
||||||
setRefreshToken(null);
|
setIsAuthenticated(false);
|
||||||
setIsAuthenticated(false);
|
navigate("/login");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function storeApiKey(apikey: string) {
|
function storeApiKey(apikey: string) {
|
||||||
|
|
@ -127,14 +129,12 @@ export function AuthProvider({ children }): React.ReactElement {
|
||||||
value={{
|
value={{
|
||||||
isAdmin,
|
isAdmin,
|
||||||
setIsAdmin,
|
setIsAdmin,
|
||||||
isAuthenticated: !!accessToken,
|
isAuthenticated,
|
||||||
accessToken,
|
accessToken,
|
||||||
refreshToken,
|
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
setUserData,
|
setUserData,
|
||||||
userData,
|
userData,
|
||||||
getAuthentication,
|
|
||||||
authenticationErrorCount: 0,
|
authenticationErrorCount: 0,
|
||||||
setAutoLogin,
|
setAutoLogin,
|
||||||
autoLogin,
|
autoLogin,
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -3,44 +3,21 @@ import { BrowserRouter } from "react-router-dom";
|
||||||
import { ReactFlowProvider } from "reactflow";
|
import { ReactFlowProvider } from "reactflow";
|
||||||
import { TooltipProvider } from "../components/ui/tooltip";
|
import { TooltipProvider } from "../components/ui/tooltip";
|
||||||
import { ApiInterceptor } from "../controllers/API/api";
|
import { ApiInterceptor } from "../controllers/API/api";
|
||||||
import { SSEProvider } from "./SSEContext";
|
|
||||||
import { AlertProvider } from "./alertContext";
|
|
||||||
import { AuthProvider } from "./authContext";
|
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 }) {
|
export default function ContextWrapper({ children }: { children: ReactNode }) {
|
||||||
//element to wrap all context
|
//element to wrap all context
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<AlertProvider>
|
<AuthProvider>
|
||||||
<AuthProvider>
|
<TooltipProvider>
|
||||||
<TooltipProvider>
|
<ReactFlowProvider>
|
||||||
<ReactFlowProvider>
|
<ApiInterceptor />
|
||||||
<DarkProvider>
|
{children}
|
||||||
<TypesProvider>
|
</ReactFlowProvider>
|
||||||
<LocationProvider>
|
</TooltipProvider>
|
||||||
<ApiInterceptor />
|
</AuthProvider>
|
||||||
<SSEProvider>
|
|
||||||
<FlowsProvider>
|
|
||||||
<UndoRedoProvider>
|
|
||||||
<StoreProvider>{children}</StoreProvider>
|
|
||||||
</UndoRedoProvider>
|
|
||||||
</FlowsProvider>
|
|
||||||
</SSEProvider>
|
|
||||||
</LocationProvider>
|
|
||||||
</TypesProvider>
|
|
||||||
</DarkProvider>
|
|
||||||
</ReactFlowProvider>
|
|
||||||
</TooltipProvider>
|
|
||||||
</AuthProvider>
|
|
||||||
</AlertProvider>
|
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { useContext, useEffect } from "react";
|
||||||
import { Cookies } from "react-cookie";
|
import { Cookies } from "react-cookie";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { renewAccessToken } from ".";
|
import { renewAccessToken } from ".";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
import useAlertStore from "../../stores/alertStore";
|
||||||
|
|
||||||
// Create a new Axios instance
|
// Create a new Axios instance
|
||||||
const api: AxiosInstance = axios.create({
|
const api: AxiosInstance = axios.create({
|
||||||
|
|
@ -12,8 +12,8 @@ const api: AxiosInstance = axios.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
function ApiInterceptor() {
|
function ApiInterceptor() {
|
||||||
const { setErrorData } = useContext(alertContext);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
let { accessToken, login, logout, authenticationErrorCount } =
|
let { accessToken, login, logout, authenticationErrorCount, autoLogin } =
|
||||||
useContext(AuthContext);
|
useContext(AuthContext);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const cookies = new Cookies();
|
const cookies = new Cookies();
|
||||||
|
|
@ -23,23 +23,22 @@ function ApiInterceptor() {
|
||||||
(response) => response,
|
(response) => response,
|
||||||
async (error: AxiosError) => {
|
async (error: AxiosError) => {
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
const refreshToken = cookies.get("refresh_tkn_lflw");
|
const accessToken = cookies.get("access_token_lf");
|
||||||
if (refreshToken && refreshToken !== "auto") {
|
if (accessToken && !autoLogin) {
|
||||||
authenticationErrorCount = authenticationErrorCount + 1;
|
authenticationErrorCount = authenticationErrorCount + 1;
|
||||||
if (authenticationErrorCount > 3) {
|
if (authenticationErrorCount > 3) {
|
||||||
authenticationErrorCount = 0;
|
authenticationErrorCount = 0;
|
||||||
logout();
|
logout();
|
||||||
navigate("/login");
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await renewAccessToken(refreshToken);
|
const res = await renewAccessToken();
|
||||||
if (res?.data?.access_token && res?.data?.refresh_token) {
|
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) {
|
if (error?.config?.headers) {
|
||||||
delete error.config.headers["Authorization"];
|
delete error.config.headers["Authorization"];
|
||||||
error.config.headers["Authorization"] = `Bearer ${cookies.get(
|
error.config.headers["Authorization"] = `Bearer ${cookies.get(
|
||||||
"access_tkn_lflw"
|
"access_token_lf"
|
||||||
)}`;
|
)}`;
|
||||||
const response = await axios.request(error.config);
|
const response = await axios.request(error.config);
|
||||||
return response;
|
return response;
|
||||||
|
|
@ -47,20 +46,17 @@ function ApiInterceptor() {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
||||||
logout();
|
logout();
|
||||||
navigate("/login");
|
|
||||||
} else {
|
} else {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
logout();
|
logout();
|
||||||
navigate("/login");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!refreshToken && error?.config?.url?.includes("login")) {
|
if (!accessToken && error?.config?.url?.includes("login")) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
} else {
|
} else {
|
||||||
logout();
|
logout();
|
||||||
navigate("/login");
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
|
// 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
|
// Request interceptor to add access token to every request
|
||||||
const requestInterceptor = api.interceptors.request.use(
|
const requestInterceptor = api.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
|
const accessToken = cookies.get("access_token_lf");
|
||||||
if (accessToken && !isAuthorizedURL(config?.url)) {
|
if (accessToken && !isAuthorizedURL(config?.url)) {
|
||||||
config.headers["Authorization"] = `Bearer ${accessToken}`;
|
config.headers["Authorization"] = `Bearer ${accessToken}`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
* @returns {Promise<AxiosResponse<any>>} A promise that resolves to an AxiosResponse containing the version information.
|
||||||
*/
|
*/
|
||||||
export async function getVersion() {
|
export async function getVersion() {
|
||||||
const respnose = await api.get(`${BASE_URL_API}version`);
|
const response = await api.get(`${BASE_URL_API}version`);
|
||||||
return respnose.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -411,11 +411,9 @@ export async function autoLogin() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function renewAccessToken(token: string) {
|
export async function renewAccessToken() {
|
||||||
try {
|
try {
|
||||||
if (token) {
|
return await api.post(`${BASE_URL_API}refresh`);
|
||||||
return await api.post(`${BASE_URL_API}refresh?token=${token}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
@ -429,7 +427,6 @@ export async function getLoggedUser(): Promise<Users | null> {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -443,7 +440,6 @@ export async function addUser(user: UserInputType): Promise<Array<Users>> {
|
||||||
}
|
}
|
||||||
return res.data;
|
return res.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -460,7 +456,6 @@ export async function getUsersPage(
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -473,7 +468,6 @@ export async function deleteUser(user_id: string) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -485,7 +479,6 @@ export async function updateUser(user_id: string, user: changeUser) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -500,7 +493,6 @@ export async function resetPassword(user_id: string, user: resetPasswordType) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -512,7 +504,6 @@ export async function getApiKey() {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -524,7 +515,6 @@ export async function createApiKey(name: string) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -536,7 +526,6 @@ export async function deleteApiKey(api_key: string) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -550,7 +539,6 @@ export async function addApiKeyStore(key: string) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -684,7 +672,6 @@ export async function getStoreComponents({
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -696,7 +683,6 @@ export async function postStoreComponents(component: Component) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -710,7 +696,6 @@ export async function getComponent(component_id: string) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -750,7 +735,6 @@ export async function searchComponent(
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -762,7 +746,6 @@ export async function checkHasApiKey() {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -774,7 +757,6 @@ export async function checkHasStore() {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -797,7 +779,6 @@ export async function getCountComponents(is_component?: boolean | null) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -809,7 +790,6 @@ export async function getStoreTags() {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -860,3 +840,13 @@ export async function updateFlowStore(
|
||||||
throw error;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import {
|
||||||
LANGFLOW_SUPPORTED_TYPES,
|
LANGFLOW_SUPPORTED_TYPES,
|
||||||
} from "../../constants/constants";
|
} from "../../constants/constants";
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
import { TemplateVariableType } from "../../types/api";
|
import { TemplateVariableType } from "../../types/api";
|
||||||
import { tweakType, uniqueTweakType } from "../../types/components";
|
import { tweakType, uniqueTweakType } from "../../types/components";
|
||||||
import { FlowType, NodeType } from "../../types/flow/index";
|
import { FlowType, NodeType } from "../../types/flow/index";
|
||||||
|
|
@ -48,16 +48,17 @@ const ApiModal = forwardRef(
|
||||||
const [activeTab, setActiveTab] = useState("0");
|
const [activeTab, setActiveTab] = useState("0");
|
||||||
const tweak = useRef<tweakType>([]);
|
const tweak = useRef<tweakType>([]);
|
||||||
const tweaksList = useRef<string[]>([]);
|
const tweaksList = useRef<string[]>([]);
|
||||||
const { setTweak, getTweak, tabsState } = useContext(FlowsContext);
|
const [getTweak, setTweak] = useState<tweakType>([]);
|
||||||
|
const flowState = useFlowStore((state) => state.flowState);
|
||||||
const pythonApiCode = getPythonApiCode(
|
const pythonApiCode = getPythonApiCode(
|
||||||
flow,
|
flow,
|
||||||
autoLogin,
|
autoLogin,
|
||||||
tweak.current,
|
tweak.current,
|
||||||
tabsState
|
flowState
|
||||||
);
|
);
|
||||||
const curl_code = getCurlCode(flow, autoLogin, tweak.current, tabsState);
|
const curl_code = getCurlCode(flow, autoLogin, tweak.current, flowState);
|
||||||
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
|
const pythonCode = getPythonCode(flow, tweak.current, flowState);
|
||||||
const widgetCode = getWidgetCode(flow, autoLogin, tabsState);
|
const widgetCode = getWidgetCode(flow, autoLogin, flowState);
|
||||||
const tweaksCode = buildTweaks(flow);
|
const tweaksCode = buildTweaks(flow);
|
||||||
const codesArray = [
|
const codesArray = [
|
||||||
curl_code,
|
curl_code,
|
||||||
|
|
@ -168,11 +169,11 @@ const ApiModal = forwardRef(
|
||||||
flow,
|
flow,
|
||||||
autoLogin,
|
autoLogin,
|
||||||
tweak.current,
|
tweak.current,
|
||||||
tabsState
|
flowState
|
||||||
);
|
);
|
||||||
const curl_code = getCurlCode(flow, autoLogin, tweak.current, tabsState);
|
const curl_code = getCurlCode(flow, autoLogin, tweak.current, flowState);
|
||||||
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
|
const pythonCode = getPythonCode(flow, tweak.current, flowState);
|
||||||
const widgetCode = getWidgetCode(flow, autoLogin, tabsState);
|
const widgetCode = getWidgetCode(flow, autoLogin, flowState);
|
||||||
|
|
||||||
tabs![0].code = curl_code;
|
tabs![0].code = curl_code;
|
||||||
tabs![1].code = pythonApiCode;
|
tabs![1].code = pythonApiCode;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,24 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
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 { nodeIconsLucide } from "../../utils/styleUtils";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
|
||||||
const Content: React.FC<ContentProps> = ({ children }) => {
|
const Content: React.FC<ContentProps> = ({ children }) => {
|
||||||
return <div className="h-full w-full">{children}</div>;
|
return <div className="h-full w-full">{children}</div>;
|
||||||
};
|
};
|
||||||
const Trigger: React.FC<ContentProps> = ({
|
const Trigger: React.FC<TriggerProps> = ({
|
||||||
children,
|
children,
|
||||||
tolltipContent,
|
tooltipContent,
|
||||||
side,
|
side,
|
||||||
}) => {
|
}: TriggerProps) => {
|
||||||
return tolltipContent ? (
|
return tooltipContent ? (
|
||||||
<ShadTooltip side={side} content={tolltipContent}>
|
<ShadTooltip side={side} content={tooltipContent}>
|
||||||
<div className="h-full w-full">{children}</div>
|
<div className="h-full w-full">{children}</div>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -23,7 +27,6 @@ const Trigger: React.FC<ContentProps> = ({
|
||||||
};
|
};
|
||||||
function ConfirmationModal({
|
function ConfirmationModal({
|
||||||
title,
|
title,
|
||||||
asChild,
|
|
||||||
titleHeader,
|
titleHeader,
|
||||||
modalContentTitle,
|
modalContentTitle,
|
||||||
cancelText,
|
cancelText,
|
||||||
|
|
@ -49,6 +52,7 @@ function ConfirmationModal({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onClose) onClose!(modalOpen);
|
if (onClose) onClose!(modalOpen);
|
||||||
}, [modalOpen]);
|
}, [modalOpen]);
|
||||||
|
|
||||||
const triggerChild = React.Children.toArray(children).find(
|
const triggerChild = React.Children.toArray(children).find(
|
||||||
(child) => (child as React.ReactElement).type === Trigger
|
(child) => (child as React.ReactElement).type === Trigger
|
||||||
);
|
);
|
||||||
|
|
@ -58,7 +62,7 @@ function ConfirmationModal({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal size={size} open={modalOpen} setOpen={setModalOpen}>
|
<BaseModal size={size} open={modalOpen} setOpen={setModalOpen}>
|
||||||
<BaseModal.Trigger asChild={asChild}>{triggerChild}</BaseModal.Trigger>
|
<BaseModal.Trigger>{triggerChild}</BaseModal.Trigger>
|
||||||
<BaseModal.Header description={titleHeader ?? null}>
|
<BaseModal.Header description={titleHeader ?? null}>
|
||||||
<span className="pr-2">{title}</span>
|
<span className="pr-2">{title}</span>
|
||||||
<Icon
|
<Icon
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { forwardRef, useContext, useEffect, useState } from "react";
|
import { forwardRef, useEffect, useState } from "react";
|
||||||
import { useUpdateNodeInternals } from "reactflow";
|
|
||||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||||
import CodeAreaComponent from "../../components/codeAreaComponent";
|
import CodeAreaComponent from "../../components/codeAreaComponent";
|
||||||
import DictComponent from "../../components/dictComponent";
|
import DictComponent from "../../components/dictComponent";
|
||||||
|
|
@ -29,11 +28,8 @@ import {
|
||||||
LANGFLOW_SUPPORTED_TYPES,
|
LANGFLOW_SUPPORTED_TYPES,
|
||||||
limitScrollFieldsModal,
|
limitScrollFieldsModal,
|
||||||
} from "../../constants/constants";
|
} from "../../constants/constants";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
|
||||||
import { NodeDataType } from "../../types/flow";
|
import { NodeDataType } from "../../types/flow";
|
||||||
import { FlowsState } from "../../types/tabs";
|
|
||||||
import {
|
import {
|
||||||
convertObjToArray,
|
convertObjToArray,
|
||||||
convertValuesToNumbers,
|
convertValuesToNumbers,
|
||||||
|
|
@ -58,13 +54,11 @@ const EditNodeModal = forwardRef(
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
|
||||||
|
|
||||||
const [myData, setMyData] = useState(data);
|
const [myData, setMyData] = useState(data);
|
||||||
|
|
||||||
const { setTabsState, tabId } = useContext(FlowsContext);
|
const setPending = useFlowStore((state) => state.setPending);
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
const edges = useFlowStore((state) => state.edges);
|
||||||
const { setModalContextOpen } = useContext(alertContext);
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
|
|
||||||
function changeAdvanced(n) {
|
function changeAdvanced(n) {
|
||||||
setMyData((old) => {
|
setMyData((old) => {
|
||||||
|
|
@ -81,14 +75,12 @@ const EditNodeModal = forwardRef(
|
||||||
newData.node!.template[name].value = newValue;
|
newData.node!.template[name].value = newValue;
|
||||||
return newData;
|
return newData;
|
||||||
});
|
});
|
||||||
updateNodeInternals(data.id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
setMyData(data); // reset data to what it is on node when opening modal
|
setMyData(data); // reset data to what it is on node when opening modal
|
||||||
}
|
}
|
||||||
setModalContextOpen(open);
|
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||||
|
|
@ -159,7 +151,7 @@ const EditNodeModal = forwardRef(
|
||||||
fieldName: templateParam,
|
fieldName: templateParam,
|
||||||
};
|
};
|
||||||
let disabled =
|
let disabled =
|
||||||
reactFlowInstance?.getEdges().some(
|
edges.some(
|
||||||
(edge) =>
|
(edge) =>
|
||||||
edge.targetHandle ===
|
edge.targetHandle ===
|
||||||
scapedJSONStringfy(
|
scapedJSONStringfy(
|
||||||
|
|
@ -535,17 +527,14 @@ const EditNodeModal = forwardRef(
|
||||||
id={"saveChangesBtn"}
|
id={"saveChangesBtn"}
|
||||||
className="mt-3"
|
className="mt-3"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
data.node = myData.node;
|
setNode(data.id, (old) => ({
|
||||||
//@ts-ignore
|
...old,
|
||||||
setTabsState((prev: FlowsState) => {
|
data: {
|
||||||
return {
|
...old.data,
|
||||||
...prev,
|
node: myData.node,
|
||||||
[tabId]: {
|
},
|
||||||
...prev[tabId],
|
}));
|
||||||
isPending: true,
|
setPending(true);
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import * as Form from "@radix-ui/react-form";
|
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 IconComponent from "../../components/genericIconComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Input } from "../../components/ui/input";
|
import { Input } from "../../components/ui/input";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { createApiKey } from "../../controllers/API";
|
import { createApiKey } from "../../controllers/API";
|
||||||
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import { ApiKeyType } from "../../types/components";
|
import { ApiKeyType } from "../../types/components";
|
||||||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
|
@ -24,7 +24,7 @@ export default function SecretKeyModal({
|
||||||
const [apiKeyValue, setApiKeyValue] = useState("");
|
const [apiKeyValue, setApiKeyValue] = useState("");
|
||||||
const [renderKey, setRenderKey] = useState(false);
|
const [renderKey, setRenderKey] = useState(false);
|
||||||
const [textCopied, setTextCopied] = useState(true);
|
const [textCopied, setTextCopied] = useState(true);
|
||||||
const { setSuccessData } = useContext(alertContext);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ import { useContext, useState } from "react";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Input } from "../../components/ui/input";
|
import { Input } from "../../components/ui/input";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
import { StoreContext } from "../../contexts/storeContext";
|
|
||||||
import { addApiKeyStore } from "../../controllers/API";
|
import { addApiKeyStore } from "../../controllers/API";
|
||||||
|
import useAlertStore from "../../stores/alertStore";
|
||||||
|
import { useStoreStore } from "../../stores/storeStore";
|
||||||
import { StoreApiKeyType } from "../../types/components";
|
import { StoreApiKeyType } from "../../types/components";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
|
||||||
|
|
@ -16,11 +16,17 @@ export default function StoreApiKeyModal({
|
||||||
}: StoreApiKeyType) {
|
}: StoreApiKeyType) {
|
||||||
if (disabled) return <>{children}</>;
|
if (disabled) return <>{children}</>;
|
||||||
const [open, setOpen] = useState(false);
|
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 { storeApiKey } = useContext(AuthContext);
|
||||||
const { hasApiKey, validApiKey } = useContext(StoreContext);
|
|
||||||
const [apiKeyValue, setApiKeyValue] = useState("");
|
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 = () => {
|
const handleSaveKey = () => {
|
||||||
if (apiKeyValue) {
|
if (apiKeyValue) {
|
||||||
addApiKeyStore(apiKeyValue).then(
|
addApiKeyStore(apiKeyValue).then(
|
||||||
|
|
@ -30,12 +36,18 @@ export default function StoreApiKeyModal({
|
||||||
});
|
});
|
||||||
storeApiKey(apiKeyValue);
|
storeApiKey(apiKeyValue);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
setHasApiKey(true);
|
||||||
|
setValidApiKey(true);
|
||||||
|
setLoadingApiKey(false);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "There was an error saving the API Key, please try again.",
|
title: "There was an error saving the API Key, please try again.",
|
||||||
list: [error["response"]["data"]["detail"]],
|
list: [error["response"]["data"]["detail"]],
|
||||||
});
|
});
|
||||||
|
setHasApiKey(false);
|
||||||
|
setValidApiKey(false);
|
||||||
|
setLoadingApiKey(false);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ const Trigger: React.FC<TriggerProps> = ({ children, asChild, disable }) => {
|
||||||
<DialogTrigger
|
<DialogTrigger
|
||||||
className={asChild ? "" : "w-full"}
|
className={asChild ? "" : "w-full"}
|
||||||
hidden={children ? false : true}
|
hidden={children ? false : true}
|
||||||
|
disabled={disable}
|
||||||
asChild={asChild}
|
asChild={asChild}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,15 @@ import "ace-builds/src-noconflict/mode-python";
|
||||||
import "ace-builds/src-noconflict/theme-github";
|
import "ace-builds/src-noconflict/theme-github";
|
||||||
import "ace-builds/src-noconflict/theme-twilight";
|
import "ace-builds/src-noconflict/theme-twilight";
|
||||||
// import "ace-builds/webpack-resolver";
|
// import "ace-builds/webpack-resolver";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import AceEditor from "react-ace";
|
import AceEditor from "react-ace";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Input } from "../../components/ui/input";
|
import { Input } from "../../components/ui/input";
|
||||||
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants/constants";
|
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 { postCustomComponent, postValidateCode } from "../../controllers/API";
|
||||||
|
import useAlertStore from "../../stores/alertStore";
|
||||||
|
import { useDarkStore } from "../../stores/darkStore";
|
||||||
import { codeAreaModalPropsType } from "../../types/components";
|
import { codeAreaModalPropsType } from "../../types/components";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
|
||||||
|
|
@ -27,10 +26,11 @@ export default function CodeAreaModal({
|
||||||
readonly = false,
|
readonly = false,
|
||||||
}: codeAreaModalPropsType): JSX.Element {
|
}: codeAreaModalPropsType): JSX.Element {
|
||||||
const [code, setCode] = useState(value);
|
const [code, setCode] = useState(value);
|
||||||
const { dark } = useContext(darkContext);
|
const dark = useDarkStore((state) => state.dark);
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
|
||||||
const [height, setHeight] = useState<string | null>(null);
|
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<{
|
const [error, setError] = useState<{
|
||||||
detail: { error: string | undefined; traceback: string | undefined };
|
detail: { error: string | undefined; traceback: string | undefined };
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
|
||||||
|
|
@ -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 EditFlowSettings from "../../components/EditFlowSettingsComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Checkbox } from "../../components/ui/checkbox";
|
import { Checkbox } from "../../components/ui/checkbox";
|
||||||
import { EXPORT_DIALOG_SUBTITLE } from "../../constants/constants";
|
import { EXPORT_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
import { useDarkStore } from "../../stores/darkStore";
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { removeApiKeys } from "../../utils/reactflowUtils";
|
import { downloadFlow, removeApiKeys } from "../../utils/reactflowUtils";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
|
||||||
const ExportModal = forwardRef(
|
const ExportModal = forwardRef(
|
||||||
(props: { children: ReactNode }, ref): JSX.Element => {
|
(props: { children: ReactNode }, ref): JSX.Element => {
|
||||||
const { flows, tabId, downloadFlow, version } = useContext(FlowsContext);
|
const version = useDarkStore((state) => state.version);
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
const setNoticeData = useAlertStore((state) => state.setNoticeData);
|
||||||
const { setNoticeData } = useContext(alertContext);
|
|
||||||
const [checked, setChecked] = useState(true);
|
const [checked, setChecked] = useState(true);
|
||||||
const flow = flows.find((f) => f.id === tabId);
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setName(flow!.name);
|
setName(currentFlow!.name);
|
||||||
setDescription(flow!.description);
|
setDescription(currentFlow!.description);
|
||||||
}, [flow!.name, flow!.description]);
|
}, [currentFlow!.name, currentFlow!.description]);
|
||||||
const [name, setName] = useState(flow!.name);
|
const [name, setName] = useState(currentFlow!.name);
|
||||||
const [description, setDescription] = useState(flow!.description);
|
const [description, setDescription] = useState(currentFlow!.description);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal size="smaller-h-full" open={open} setOpen={setOpen}>
|
<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}>
|
<BaseModal.Header description={EXPORT_DIALOG_SUBTITLE}>
|
||||||
<span className="pr-2">Export</span>
|
<span className="pr-2">Export</span>
|
||||||
<IconComponent
|
<IconComponent
|
||||||
|
|
@ -67,8 +66,8 @@ const ExportModal = forwardRef(
|
||||||
if (checked) {
|
if (checked) {
|
||||||
downloadFlow(
|
downloadFlow(
|
||||||
{
|
{
|
||||||
id: tabId,
|
id: currentFlow!.id,
|
||||||
data: flow!.data!,
|
data: currentFlow!.data!,
|
||||||
description,
|
description,
|
||||||
name,
|
name,
|
||||||
last_tested_version: version,
|
last_tested_version: version,
|
||||||
|
|
@ -84,8 +83,8 @@ const ExportModal = forwardRef(
|
||||||
} else
|
} else
|
||||||
downloadFlow(
|
downloadFlow(
|
||||||
removeApiKeys({
|
removeApiKeys({
|
||||||
id: tabId,
|
id: currentFlow!.id,
|
||||||
data: flow!.data!,
|
data: currentFlow!.data!,
|
||||||
description,
|
description,
|
||||||
name,
|
name,
|
||||||
last_tested_version: version,
|
last_tested_version: version,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
|
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
|
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { FlowSettingsPropsType } from "../../types/components";
|
import { FlowSettingsPropsType } from "../../types/components";
|
||||||
import { FlowType } from "../../types/flow";
|
import { FlowType } from "../../types/flow";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
|
@ -12,20 +12,21 @@ export default function FlowSettingsModal({
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
}: FlowSettingsPropsType): JSX.Element {
|
}: FlowSettingsPropsType): JSX.Element {
|
||||||
const { flows, tabId, saveFlow } = useContext(FlowsContext);
|
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
|
||||||
const flow = flows.find((f) => f.id === tabId);
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
|
const flows = useFlowsManagerStore((state) => state.flows);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setName(flow!.name);
|
setName(currentFlow!.name);
|
||||||
setDescription(flow!.description);
|
setDescription(currentFlow!.description);
|
||||||
}, [flow!.name, flow!.description]);
|
}, [currentFlow!.name, currentFlow!.description, open]);
|
||||||
const [name, setName] = useState(flow!.name);
|
|
||||||
const [description, setDescription] = useState(flow!.description);
|
const [name, setName] = useState(currentFlow!.name);
|
||||||
|
const [description, setDescription] = useState(currentFlow!.description);
|
||||||
|
|
||||||
function handleClick(): void {
|
function handleClick(): void {
|
||||||
let savedFlow = flows.find((flow) => flow.id === tabId);
|
currentFlow!.name = name;
|
||||||
savedFlow!.name = name;
|
currentFlow!.description = description;
|
||||||
savedFlow!.description = description;
|
saveFlow(currentFlow!);
|
||||||
saveFlow(savedFlow!);
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +37,7 @@ export default function FlowSettingsModal({
|
||||||
flows.forEach((flow: FlowType) => {
|
flows.forEach((flow: FlowType) => {
|
||||||
if ((flow.is_component ?? false) === false) tempNameList.push(flow.name);
|
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]);
|
}, [flows]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -57,7 +58,7 @@ export default function FlowSettingsModal({
|
||||||
|
|
||||||
<BaseModal.Footer>
|
<BaseModal.Footer>
|
||||||
<Button
|
<Button
|
||||||
disabled={nameLists.includes(name) && name !== flow!.name}
|
disabled={nameLists.includes(name) && name !== currentFlow!.name}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
|
||||||
import { sendAllProps } from "../../types/api";
|
import { sendAllProps } from "../../types/api";
|
||||||
import { ChatMessageType } from "../../types/chat";
|
import { ChatMessageType } from "../../types/chat";
|
||||||
import { FlowType } from "../../types/flow";
|
import { FlowType } from "../../types/flow";
|
||||||
|
|
@ -8,7 +6,7 @@ import { classNames } from "../../utils/utils";
|
||||||
import ChatInput from "./chatInput";
|
import ChatInput from "./chatInput";
|
||||||
import ChatMessage from "./chatMessage";
|
import ChatMessage from "./chatMessage";
|
||||||
|
|
||||||
import _ from "lodash";
|
import _, { cloneDeep } from "lodash";
|
||||||
import AccordionComponent from "../../components/AccordionComponent";
|
import AccordionComponent from "../../components/AccordionComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import ToggleShadComponent from "../../components/toggleShadComponent";
|
import ToggleShadComponent from "../../components/toggleShadComponent";
|
||||||
|
|
@ -24,9 +22,10 @@ import {
|
||||||
import { Textarea } from "../../components/ui/textarea";
|
import { Textarea } from "../../components/ui/textarea";
|
||||||
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
|
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
|
||||||
import { getBuildStatus } from "../../controllers/API";
|
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";
|
import { validateNodes } from "../../utils/reactflowUtils";
|
||||||
|
|
||||||
export default function FormModal({
|
export default function FormModal({
|
||||||
|
|
@ -38,15 +37,17 @@ export default function FormModal({
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
flow: FlowType;
|
flow: FlowType;
|
||||||
}): JSX.Element {
|
}): 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(() => {
|
const [chatValue, setChatValue] = useState(() => {
|
||||||
try {
|
try {
|
||||||
const { formKeysData } = tabsState[flow.id];
|
if (!flowState) {
|
||||||
if (!formKeysData) {
|
throw new Error("flowState is undefined");
|
||||||
throw new Error("formKeysData is undefined");
|
|
||||||
}
|
}
|
||||||
const inputKeys = formKeysData.input_keys;
|
const inputKeys = flowState.input_keys;
|
||||||
const handleKeys = formKeysData.handle_keys;
|
const handleKeys = flowState.handle_keys;
|
||||||
|
|
||||||
const keyToUse = Object.keys(inputKeys!).find(
|
const keyToUse = Object.keys(inputKeys!).find(
|
||||||
(key) => !handleKeys?.some((j) => j === key) && inputKeys![key] === ""
|
(key) => !handleKeys?.some((j) => j === key) && inputKeys![key] === ""
|
||||||
|
|
@ -61,24 +62,20 @@ export default function FormModal({
|
||||||
});
|
});
|
||||||
|
|
||||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||||
const template = useRef(tabsState[flow.id].formKeysData.template);
|
const template = useRef(flowState?.template ?? undefined);
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
|
||||||
const { accessToken } = useContext(AuthContext);
|
const { accessToken } = useContext(AuthContext);
|
||||||
const { setErrorData } = useContext(alertContext);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const ws = useRef<WebSocket | null>(null);
|
const ws = useRef<WebSocket | null>(null);
|
||||||
const [lockChat, setLockChat] = useState(false);
|
const [lockChat, setLockChat] = useState(false);
|
||||||
const isOpen = useRef(open);
|
const isOpen = useRef(open);
|
||||||
const messagesRef = useRef<HTMLDivElement | null>(null);
|
const messagesRef = useRef<HTMLDivElement | null>(null);
|
||||||
const id = useRef(flow.id);
|
|
||||||
const tabsStateFlowId = tabsState[flow.id];
|
|
||||||
const tabsStateFlowIdFormKeysData = tabsStateFlowId.formKeysData;
|
|
||||||
const [chatKey, setChatKey] = useState(() => {
|
const [chatKey, setChatKey] = useState(() => {
|
||||||
if (tabsState[flow.id]?.formKeysData?.input_keys) {
|
if (flowState?.input_keys) {
|
||||||
return Object.keys(tabsState[flow.id].formKeysData.input_keys!).find(
|
return Object.keys(flowState.input_keys!).find(
|
||||||
(key) =>
|
(key) =>
|
||||||
!tabsState[flow.id].formKeysData.handle_keys!.some(
|
!flowState.handle_keys!.some((j) => j === key) &&
|
||||||
(j) => j === key
|
flowState.input_keys![key] === ""
|
||||||
) && tabsState[flow.id].formKeysData.input_keys![key] === ""
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// TODO: return a sensible default
|
// TODO: return a sensible default
|
||||||
|
|
@ -93,9 +90,6 @@ export default function FormModal({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isOpen.current = open;
|
isOpen.current = open;
|
||||||
}, [open]);
|
}, [open]);
|
||||||
useEffect(() => {
|
|
||||||
id.current = flow.id;
|
|
||||||
}, [flow.id, tabsStateFlowId, tabsStateFlowIdFormKeysData]);
|
|
||||||
|
|
||||||
var isStream = false;
|
var isStream = false;
|
||||||
|
|
||||||
|
|
@ -296,7 +290,7 @@ export default function FormModal({
|
||||||
function connectWS(): void {
|
function connectWS(): void {
|
||||||
try {
|
try {
|
||||||
const urlWs = getWebSocketUrl(
|
const urlWs = getWebSocketUrl(
|
||||||
id.current,
|
flow.id,
|
||||||
process.env.NODE_ENV === "development"
|
process.env.NODE_ENV === "development"
|
||||||
);
|
);
|
||||||
const newWs = new WebSocket(urlWs);
|
const newWs = new WebSocket(urlWs);
|
||||||
|
|
@ -384,13 +378,10 @@ export default function FormModal({
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
function sendMessage(): void {
|
function sendMessage(): void {
|
||||||
let nodeValidationErrors = validateNodes(
|
let nodeValidationErrors = validateNodes(nodes, edges);
|
||||||
reactFlowInstance!.getNodes(),
|
|
||||||
reactFlowInstance!.getEdges()
|
|
||||||
);
|
|
||||||
if (nodeValidationErrors.length === 0) {
|
if (nodeValidationErrors.length === 0) {
|
||||||
setLockChat(true);
|
setLockChat(true);
|
||||||
let inputs = tabsState[id.current].formKeysData.input_keys;
|
let inputs = flowState?.input_keys;
|
||||||
setChatValue("");
|
setChatValue("");
|
||||||
const message = inputs;
|
const message = inputs;
|
||||||
addChatHistory(message!, true, chatKey!, template.current);
|
addChatHistory(message!, true, chatKey!, template.current);
|
||||||
|
|
@ -402,13 +393,13 @@ export default function FormModal({
|
||||||
description: flow.description,
|
description: flow.description,
|
||||||
chatKey: chatKey!,
|
chatKey: chatKey!,
|
||||||
});
|
});
|
||||||
//@ts-ignore
|
if (flowState && chatKey) {
|
||||||
setTabsState((old: FlowsState) => {
|
setFlowState((old: FlowState | undefined) => {
|
||||||
if (!chatKey) return old;
|
let newFlowState = cloneDeep(old!);
|
||||||
let newTabsState = _.cloneDeep(old);
|
newFlowState.input_keys![chatKey] = "";
|
||||||
newTabsState[id.current].formKeysData.input_keys![chatKey] = "";
|
return newFlowState;
|
||||||
return newTabsState;
|
});
|
||||||
});
|
}
|
||||||
} else {
|
} else {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "Oops! Looks like you missed some required information:",
|
title: "Oops! Looks like you missed some required information:",
|
||||||
|
|
@ -418,7 +409,7 @@ export default function FormModal({
|
||||||
}
|
}
|
||||||
function clearChat(): void {
|
function clearChat(): void {
|
||||||
setChatHistory([]);
|
setChatHistory([]);
|
||||||
template.current = tabsState[id.current].formKeysData.template;
|
template.current = flowState?.template;
|
||||||
ws.current?.send(JSON.stringify({ clear_history: true }));
|
ws.current?.send(JSON.stringify({ clear_history: true }));
|
||||||
if (lockChat) setLockChat(false);
|
if (lockChat) setLockChat(false);
|
||||||
}
|
}
|
||||||
|
|
@ -426,7 +417,7 @@ export default function FormModal({
|
||||||
function handleOnCheckedChange(checked: boolean, i: string) {
|
function handleOnCheckedChange(checked: boolean, i: string) {
|
||||||
if (checked === true) {
|
if (checked === true) {
|
||||||
setChatKey(i);
|
setChatKey(i);
|
||||||
setChatValue(tabsState[flow.id].formKeysData.input_keys![i]);
|
setChatValue(flowState?.input_keys![i] ?? "");
|
||||||
} else {
|
} else {
|
||||||
setChatKey(null!);
|
setChatKey(null!);
|
||||||
setChatValue("");
|
setChatValue("");
|
||||||
|
|
@ -435,7 +426,7 @@ export default function FormModal({
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger hidden></DialogTrigger>
|
<DialogTrigger hidden></DialogTrigger>
|
||||||
{tabsState[flow.id].formKeysData && (
|
{flowState && flowState && (
|
||||||
<DialogContent className="min-w-[80vw]">
|
<DialogContent className="min-w-[80vw]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center">
|
<DialogTitle className="flex items-center">
|
||||||
|
|
@ -471,10 +462,8 @@ export default function FormModal({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{tabsState[id.current]?.formKeysData?.input_keys
|
{flowState?.input_keys
|
||||||
? Object.keys(
|
? Object.keys(flowState?.input_keys!).map((key, index) => (
|
||||||
tabsState[id.current].formKeysData.input_keys!
|
|
||||||
).map((key, index) => (
|
|
||||||
<div className="file-component-accordion-div" key={index}>
|
<div className="file-component-accordion-div" key={index}>
|
||||||
<AccordionComponent
|
<AccordionComponent
|
||||||
trigger={
|
trigger={
|
||||||
|
|
@ -495,9 +484,7 @@ export default function FormModal({
|
||||||
handleOnCheckedChange(value, key)
|
handleOnCheckedChange(value, key)
|
||||||
}
|
}
|
||||||
size="small"
|
size="small"
|
||||||
disabled={tabsState[
|
disabled={flowState.handle_keys!.some(
|
||||||
id.current
|
|
||||||
].formKeysData.handle_keys!.some(
|
|
||||||
(t) => t === key
|
(t) => t === key
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -508,30 +495,23 @@ export default function FormModal({
|
||||||
keyValue={key}
|
keyValue={key}
|
||||||
>
|
>
|
||||||
<div className="file-component-tab-column">
|
<div className="file-component-tab-column">
|
||||||
{tabsState[id.current].formKeysData.handle_keys!.some(
|
{flowState?.handle_keys!.some((t) => t === key) && (
|
||||||
(t) => t === key
|
|
||||||
) && (
|
|
||||||
<div className="font-normal text-muted-foreground ">
|
<div className="font-normal text-muted-foreground ">
|
||||||
Source: Component
|
Source: Component
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Textarea
|
<Textarea
|
||||||
className="custom-scroll"
|
className="custom-scroll"
|
||||||
value={
|
value={flowState?.input_keys![key]}
|
||||||
tabsState[id.current].formKeysData.input_keys![
|
|
||||||
key
|
|
||||||
]
|
|
||||||
}
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
//@ts-ignore
|
if (flowState) {
|
||||||
setTabsState((old: FlowsState) => {
|
setFlowState((old: FlowState | undefined) => {
|
||||||
let newTabsState = _.cloneDeep(old);
|
let newFlowState = cloneDeep(old!);
|
||||||
newTabsState[
|
newFlowState.input_keys![key] =
|
||||||
id.current
|
e.target.value;
|
||||||
].formKeysData.input_keys![key] =
|
return newFlowState;
|
||||||
e.target.value;
|
});
|
||||||
return newTabsState;
|
}
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
disabled={chatKey === key}
|
disabled={chatKey === key}
|
||||||
placeholder="Enter text..."
|
placeholder="Enter text..."
|
||||||
|
|
@ -541,37 +521,35 @@ export default function FormModal({
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
{tabsState[id.current].formKeysData.memory_keys!.map(
|
{flowState?.memory_keys!.map((key, index) => (
|
||||||
(key, index) => (
|
<div className="file-component-accordion-div" key={index}>
|
||||||
<div className="file-component-accordion-div" key={index}>
|
<AccordionComponent
|
||||||
<AccordionComponent
|
trigger={
|
||||||
trigger={
|
<div className="file-component-badge-div">
|
||||||
<div className="file-component-badge-div">
|
<Badge variant="gray" size="md">
|
||||||
<Badge variant="gray" size="md">
|
{key}
|
||||||
{key}
|
</Badge>
|
||||||
</Badge>
|
<div className="-mb-1">
|
||||||
<div className="-mb-1">
|
<ToggleShadComponent
|
||||||
<ToggleShadComponent
|
enabled={chatKey === key}
|
||||||
enabled={chatKey === key}
|
setEnabled={() => {}}
|
||||||
setEnabled={() => {}}
|
size="small"
|
||||||
size="small"
|
disabled={true}
|
||||||
disabled={true}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
key={index}
|
|
||||||
keyValue={key}
|
|
||||||
>
|
|
||||||
<div className="file-component-tab-column">
|
|
||||||
<div className="font-normal text-muted-foreground ">
|
|
||||||
Source: Memory
|
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
<div className="eraser-column-arrangement">
|
<div className="eraser-column-arrangement">
|
||||||
<div className="eraser-size">
|
<div className="eraser-size">
|
||||||
|
|
@ -635,14 +613,13 @@ export default function FormModal({
|
||||||
sendMessage={sendMessage}
|
sendMessage={sendMessage}
|
||||||
setChatValue={(value) => {
|
setChatValue={(value) => {
|
||||||
setChatValue(value);
|
setChatValue(value);
|
||||||
//@ts-ignore
|
if (flowState && chatKey) {
|
||||||
setTabsState((old: FlowsState) => {
|
setFlowState((old: FlowState | undefined) => {
|
||||||
let newTabsState = _.cloneDeep(old);
|
let newFlowState = cloneDeep(old!);
|
||||||
newTabsState[id.current].formKeysData.input_keys![
|
newFlowState.input_keys![chatKey] = value;
|
||||||
chatKey!
|
return newFlowState;
|
||||||
] = value;
|
});
|
||||||
return newTabsState;
|
}
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
inputRef={ref}
|
inputRef={ref}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import SanitizedHTMLWrapper from "../../components/SanitizedHTMLWrapper";
|
import SanitizedHTMLWrapper from "../../components/SanitizedHTMLWrapper";
|
||||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
|
|
@ -13,8 +13,8 @@ import {
|
||||||
regexHighlight,
|
regexHighlight,
|
||||||
} from "../../constants/constants";
|
} from "../../constants/constants";
|
||||||
import { TypeModal } from "../../constants/enums";
|
import { TypeModal } from "../../constants/enums";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { postValidatePrompt } from "../../controllers/API";
|
import { postValidatePrompt } from "../../controllers/API";
|
||||||
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import { genericModalPropsType } from "../../types/components";
|
import { genericModalPropsType } from "../../types/components";
|
||||||
import { handleKeyDown } from "../../utils/reactflowUtils";
|
import { handleKeyDown } from "../../utils/reactflowUtils";
|
||||||
import { classNames, varHighlightHTML } from "../../utils/utils";
|
import { classNames, varHighlightHTML } from "../../utils/utils";
|
||||||
|
|
@ -40,9 +40,10 @@ export default function GenericModal({
|
||||||
const [inputValue, setInputValue] = useState(value);
|
const [inputValue, setInputValue] = useState(value);
|
||||||
const [isEdit, setIsEdit] = useState(true);
|
const [isEdit, setIsEdit] = useState(true);
|
||||||
const [wordsHighlight, setWordsHighlight] = useState<string[]>([]);
|
const [wordsHighlight, setWordsHighlight] = useState<string[]>([]);
|
||||||
const { setErrorData, setSuccessData, setNoticeData, setModalContextOpen } =
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
useContext(alertContext);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const ref = useRef();
|
const setNoticeData = useAlertStore((state) => state.setNoticeData);
|
||||||
|
const textRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const divRef = useRef(null);
|
const divRef = useRef(null);
|
||||||
const divRefPrompt = useRef(null);
|
const divRefPrompt = useRef(null);
|
||||||
|
|
||||||
|
|
@ -155,10 +156,6 @@ export default function GenericModal({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setModalContextOpen(modalOpen);
|
|
||||||
}, [modalOpen]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal
|
<BaseModal
|
||||||
onChangeOpenModal={(open) => {}}
|
onChangeOpenModal={(open) => {}}
|
||||||
|
|
@ -228,8 +225,7 @@ export default function GenericModal({
|
||||||
/>
|
/>
|
||||||
) : type !== TypeModal.PROMPT ? (
|
) : type !== TypeModal.PROMPT ? (
|
||||||
<Textarea
|
<Textarea
|
||||||
//@ts-ignore
|
ref={textRef}
|
||||||
ref={ref}
|
|
||||||
className="form-input h-full w-full rounded-lg focus-visible:ring-1"
|
className="form-input h-full w-full rounded-lg focus-visible:ring-1"
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import { Loader2 } from "lucide-react";
|
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 EditFlowSettings from "../../components/EditFlowSettingsComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import { TagsSelector } from "../../components/tagsSelectorComponent";
|
import { TagsSelector } from "../../components/tagsSelectorComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Checkbox } from "../../components/ui/checkbox";
|
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 {
|
import {
|
||||||
getStoreComponents,
|
getStoreComponents,
|
||||||
getStoreTags,
|
getStoreTags,
|
||||||
saveFlowStore,
|
saveFlowStore,
|
||||||
updateFlowStore,
|
updateFlowStore,
|
||||||
} from "../../controllers/API";
|
} 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 { FlowType } from "../../types/flow";
|
||||||
import {
|
import {
|
||||||
downloadNode,
|
downloadNode,
|
||||||
|
|
@ -40,10 +40,12 @@ export default function ShareModal({
|
||||||
setOpen?: (open: boolean) => void;
|
setOpen?: (open: boolean) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { version, addFlow, downloadFlow } = useContext(FlowsContext);
|
const version = useDarkStore((state) => state.version);
|
||||||
const { hasApiKey, hasStore } = useContext(StoreContext);
|
const hasStore = useStoreStore((state) => state.hasStore);
|
||||||
const { setSuccessData, setErrorData } = useContext(alertContext);
|
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
|
||||||
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const [internalOpen, internalSetOpen] = useState(children ? false : true);
|
const [internalOpen, internalSetOpen] = useState(children ? false : true);
|
||||||
const [openConfirmationModal, setOpenConfirmationModal] = useState(false);
|
const [openConfirmationModal, setOpenConfirmationModal] = useState(false);
|
||||||
const nameComponent = is_component ? "component" : "flow";
|
const nameComponent = is_component ? "component" : "flow";
|
||||||
|
|
@ -55,7 +57,7 @@ export default function ShareModal({
|
||||||
const [unavaliableNames, setUnavaliableNames] = useState<
|
const [unavaliableNames, setUnavaliableNames] = useState<
|
||||||
{ id: string; name: string }[]
|
{ id: string; name: string }[]
|
||||||
>([]);
|
>([]);
|
||||||
const { saveFlow, flows, tabId } = useContext(FlowsContext);
|
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
|
||||||
|
|
||||||
const [loadingNames, setLoadingNames] = useState(false);
|
const [loadingNames, setLoadingNames] = useState(false);
|
||||||
|
|
||||||
|
|
@ -180,9 +182,6 @@ export default function ShareModal({
|
||||||
Warning: This action cannot be undone.
|
Warning: This action cannot be undone.
|
||||||
</span>
|
</span>
|
||||||
</ConfirmationModal.Content>
|
</ConfirmationModal.Content>
|
||||||
<ConfirmationModal.Trigger>
|
|
||||||
<></>
|
|
||||||
</ConfirmationModal.Trigger>
|
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import { useNavigate } from "react-router-dom";
|
||||||
import { Button } from "../../../components/ui/button";
|
import { Button } from "../../../components/ui/button";
|
||||||
import { Input } from "../../../components/ui/input";
|
import { Input } from "../../../components/ui/input";
|
||||||
import { CONTROL_LOGIN_STATE } from "../../../constants/constants";
|
import { CONTROL_LOGIN_STATE } from "../../../constants/constants";
|
||||||
import { alertContext } from "../../../contexts/alertContext";
|
|
||||||
import { AuthContext } from "../../../contexts/authContext";
|
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 { LoginType } from "../../../types/api";
|
||||||
import {
|
import {
|
||||||
inputHandlerEventType,
|
inputHandlerEventType,
|
||||||
|
|
@ -17,11 +17,10 @@ export default function LoginAdminPage() {
|
||||||
|
|
||||||
const [inputState, setInputState] =
|
const [inputState, setInputState] =
|
||||||
useState<loginInputStateType>(CONTROL_LOGIN_STATE);
|
useState<loginInputStateType>(CONTROL_LOGIN_STATE);
|
||||||
const { login, getAuthentication, setUserData } = useContext(AuthContext);
|
const { login, isAuthenticated, setUserData } = useContext(AuthContext);
|
||||||
|
|
||||||
const { password, username } = inputState;
|
const { password, username } = inputState;
|
||||||
const { setErrorData } = useContext(alertContext);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
|
||||||
function handleInput({
|
function handleInput({
|
||||||
target: { name, value },
|
target: { name, value },
|
||||||
}: inputHandlerEventType): void {
|
}: inputHandlerEventType): void {
|
||||||
|
|
@ -35,8 +34,7 @@ export default function LoginAdminPage() {
|
||||||
};
|
};
|
||||||
onLogin(user)
|
onLogin(user)
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
login(user.access_token, user.refresh_token);
|
login(user.access_token);
|
||||||
getUser();
|
|
||||||
navigate("/admin/");
|
navigate("/admin/");
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.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 (
|
return (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center bg-muted">
|
<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">
|
<div className="flex w-72 flex-col items-center justify-center gap-2">
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import IconComponent from "../../components/genericIconComponent";
|
||||||
import Header from "../../components/headerComponent";
|
import Header from "../../components/headerComponent";
|
||||||
import LoadingComponent from "../../components/loadingComponent";
|
import LoadingComponent from "../../components/loadingComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
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 { Input } from "../../components/ui/input";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
|
|
@ -20,9 +20,7 @@ import {
|
||||||
ADMIN_HEADER_DESCRIPTION,
|
ADMIN_HEADER_DESCRIPTION,
|
||||||
ADMIN_HEADER_TITLE,
|
ADMIN_HEADER_TITLE,
|
||||||
} from "../../constants/constants";
|
} from "../../constants/constants";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
|
||||||
import {
|
import {
|
||||||
addUser,
|
addUser,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
|
|
@ -31,6 +29,8 @@ import {
|
||||||
} from "../../controllers/API";
|
} from "../../controllers/API";
|
||||||
import ConfirmationModal from "../../modals/ConfirmationModal";
|
import ConfirmationModal from "../../modals/ConfirmationModal";
|
||||||
import UserManagementModal from "../../modals/UserManagementModal";
|
import UserManagementModal from "../../modals/UserManagementModal";
|
||||||
|
import useAlertStore from "../../stores/alertStore";
|
||||||
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { Users } from "../../types/api";
|
import { Users } from "../../types/api";
|
||||||
import { UserInputType } from "../../types/components";
|
import { UserInputType } from "../../types/components";
|
||||||
|
|
||||||
|
|
@ -40,15 +40,17 @@ export default function AdminPage() {
|
||||||
const [size, setPageSize] = useState(10);
|
const [size, setPageSize] = useState(10);
|
||||||
const [index, setPageIndex] = useState(1);
|
const [index, setPageIndex] = useState(1);
|
||||||
const [loadingUsers, setLoadingUsers] = useState(true);
|
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 { userData } = useContext(AuthContext);
|
||||||
const [totalRowsCount, setTotalRowsCount] = useState(0);
|
const [totalRowsCount, setTotalRowsCount] = useState(0);
|
||||||
|
const setCurrentFlowId = useFlowsManagerStore(
|
||||||
const { setTabId } = useContext(FlowsContext);
|
(state) => state.setCurrentFlowId
|
||||||
|
);
|
||||||
|
|
||||||
// set null id
|
// set null id
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTabId("");
|
setCurrentFlowId("");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const userList = useRef([]);
|
const userList = useRef([]);
|
||||||
|
|
@ -308,7 +310,7 @@ export default function AdminPage() {
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="relative left-1 truncate py-2 text-align-last-left">
|
<TableCell className="relative left-1 truncate py-2 text-align-last-left">
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
asChild
|
size="x-small"
|
||||||
title="Edit"
|
title="Edit"
|
||||||
titleHeader={`${user.username}`}
|
titleHeader={`${user.username}`}
|
||||||
modalContentTitle="Attention!"
|
modalContentTitle="Attention!"
|
||||||
|
|
@ -333,17 +335,14 @@ export default function AdminPage() {
|
||||||
</ConfirmationModal.Content>
|
</ConfirmationModal.Content>
|
||||||
<ConfirmationModal.Trigger>
|
<ConfirmationModal.Trigger>
|
||||||
<div className="flex w-fit">
|
<div className="flex w-fit">
|
||||||
<Checkbox
|
<CheckBoxDiv checked={user.is_active} />
|
||||||
id="is_active"
|
|
||||||
checked={user.is_active}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</ConfirmationModal.Trigger>
|
</ConfirmationModal.Trigger>
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="relative left-1 truncate py-2 text-align-last-left">
|
<TableCell className="relative left-1 truncate py-2 text-align-last-left">
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
asChild
|
size="x-small"
|
||||||
title="Edit"
|
title="Edit"
|
||||||
titleHeader={`${user.username}`}
|
titleHeader={`${user.username}`}
|
||||||
modalContentTitle="Attention!"
|
modalContentTitle="Attention!"
|
||||||
|
|
@ -368,10 +367,7 @@ export default function AdminPage() {
|
||||||
</ConfirmationModal.Content>
|
</ConfirmationModal.Content>
|
||||||
<ConfirmationModal.Trigger>
|
<ConfirmationModal.Trigger>
|
||||||
<div className="flex w-fit">
|
<div className="flex w-fit">
|
||||||
<Checkbox
|
<CheckBoxDiv checked={user.is_superuser} />
|
||||||
id="is_superuser"
|
|
||||||
checked={user.is_superuser}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</ConfirmationModal.Trigger>
|
</ConfirmationModal.Trigger>
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
|
|
@ -413,6 +409,7 @@ export default function AdminPage() {
|
||||||
</UserManagementModal>
|
</UserManagementModal>
|
||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
|
size="x-small"
|
||||||
title="Delete"
|
title="Delete"
|
||||||
titleHeader="Delete User"
|
titleHeader="Delete User"
|
||||||
modalContentTitle="Attention!"
|
modalContentTitle="Attention!"
|
||||||
|
|
@ -432,12 +429,10 @@ export default function AdminPage() {
|
||||||
</span>
|
</span>
|
||||||
</ConfirmationModal.Content>
|
</ConfirmationModal.Content>
|
||||||
<ConfirmationModal.Trigger>
|
<ConfirmationModal.Trigger>
|
||||||
<ShadTooltip content="Delete" side="top">
|
<IconComponent
|
||||||
<IconComponent
|
name="Trash2"
|
||||||
name="Trash2"
|
className="ml-2 h-4 w-4 cursor-pointer"
|
||||||
className="ml-2 h-4 w-4 cursor-pointer"
|
/>
|
||||||
/>
|
|
||||||
</ShadTooltip>
|
|
||||||
</ConfirmationModal.Trigger>
|
</ConfirmationModal.Trigger>
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import {
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "../../components/ui/table";
|
} from "../../components/ui/table";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
import { deleteApiKey, getApiKey } from "../../controllers/API";
|
import { deleteApiKey, getApiKey } from "../../controllers/API";
|
||||||
import ConfirmationModal from "../../modals/ConfirmationModal";
|
import ConfirmationModal from "../../modals/ConfirmationModal";
|
||||||
|
|
@ -25,11 +24,13 @@ import {
|
||||||
LAST_USED_SPAN_1,
|
LAST_USED_SPAN_1,
|
||||||
LAST_USED_SPAN_2,
|
LAST_USED_SPAN_2,
|
||||||
} from "../../constants/constants";
|
} from "../../constants/constants";
|
||||||
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import { ApiKey } from "../../types/components";
|
import { ApiKey } from "../../types/components";
|
||||||
|
|
||||||
export default function ApiKeysPage() {
|
export default function ApiKeysPage() {
|
||||||
const [loadingKeys, setLoadingKeys] = useState(true);
|
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 { userData } = useContext(AuthContext);
|
||||||
const [userId, setUserId] = useState("");
|
const [userId, setUserId] = useState("");
|
||||||
const keysList = useRef([]);
|
const keysList = useRef([]);
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,34 @@
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import {
|
import { MouseEvent, useCallback, useEffect, useRef, useState } from "react";
|
||||||
MouseEvent,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Background,
|
Background,
|
||||||
Connection,
|
Connection,
|
||||||
Controls,
|
Controls,
|
||||||
Edge,
|
Edge,
|
||||||
EdgeChange,
|
|
||||||
NodeChange,
|
|
||||||
NodeDragHandler,
|
NodeDragHandler,
|
||||||
|
OnMove,
|
||||||
OnSelectionChangeParams,
|
OnSelectionChangeParams,
|
||||||
SelectionDragHandler,
|
SelectionDragHandler,
|
||||||
addEdge,
|
addEdge,
|
||||||
updateEdge,
|
updateEdge,
|
||||||
useEdgesState,
|
|
||||||
useNodesState,
|
|
||||||
useReactFlow,
|
|
||||||
} from "reactflow";
|
} from "reactflow";
|
||||||
import GenericNode from "../../../../CustomNodes/GenericNode";
|
import GenericNode from "../../../../CustomNodes/GenericNode";
|
||||||
import Chat from "../../../../components/chatComponent";
|
import Chat from "../../../../components/chatComponent";
|
||||||
import Loading from "../../../../components/ui/loading";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import { alertContext } from "../../../../contexts/alertContext";
|
import useFlowStore from "../../../../stores/flowStore";
|
||||||
import { FlowsContext } from "../../../../contexts/flowsContext";
|
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||||
import { locationContext } from "../../../../contexts/locationContext";
|
import { useTypesStore } from "../../../../stores/typesStore";
|
||||||
import { typesContext } from "../../../../contexts/typesContext";
|
|
||||||
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
|
|
||||||
import { APIClassType } from "../../../../types/api";
|
import { APIClassType } from "../../../../types/api";
|
||||||
import { FlowType, NodeType, targetHandleType } from "../../../../types/flow";
|
import { FlowType, NodeType, targetHandleType } from "../../../../types/flow";
|
||||||
import { FlowsState } from "../../../../types/tabs";
|
|
||||||
import {
|
import {
|
||||||
generateFlow,
|
generateFlow,
|
||||||
generateNodeFromFlow,
|
generateNodeFromFlow,
|
||||||
|
getNodeId,
|
||||||
isValidConnection,
|
isValidConnection,
|
||||||
scapeJSONParse,
|
scapeJSONParse,
|
||||||
validateSelection,
|
validateSelection,
|
||||||
} from "../../../../utils/reactflowUtils";
|
} from "../../../../utils/reactflowUtils";
|
||||||
import { cn, getRandomName, isWrappedWithClass } from "../../../../utils/utils";
|
import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
|
||||||
import ConnectionLineComponent from "../ConnectionLineComponent";
|
import ConnectionLineComponent from "../ConnectionLineComponent";
|
||||||
import SelectionMenu from "../SelectionMenuComponent";
|
import SelectionMenu from "../SelectionMenuComponent";
|
||||||
import ExtraSidebar from "../extraSidebarComponent";
|
import ExtraSidebar from "../extraSidebarComponent";
|
||||||
|
|
@ -57,45 +44,58 @@ export default function Page({
|
||||||
flow: FlowType;
|
flow: FlowType;
|
||||||
view?: boolean;
|
view?: boolean;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
let {
|
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||||
updateFlow,
|
const autoSaveCurrentFlow = useFlowsManagerStore(
|
||||||
uploadFlow,
|
(state) => state.autoSaveCurrentFlow
|
||||||
getNodeId,
|
);
|
||||||
paste,
|
const types = useTypesStore((state) => state.types);
|
||||||
lastCopiedSelection,
|
const templates = useTypesStore((state) => state.templates);
|
||||||
setLastCopiedSelection,
|
const setFilterEdge = useTypesStore((state) => state.setFilterEdge);
|
||||||
tabsState,
|
|
||||||
saveFlow,
|
|
||||||
setTabsState,
|
|
||||||
tabId,
|
|
||||||
saveCurrentFlow,
|
|
||||||
} = useContext(FlowsContext);
|
|
||||||
const {
|
|
||||||
types,
|
|
||||||
reactFlowInstance,
|
|
||||||
setReactFlowInstance,
|
|
||||||
templates,
|
|
||||||
setFilterEdge,
|
|
||||||
deleteNode,
|
|
||||||
deleteEdge,
|
|
||||||
} = useContext(typesContext);
|
|
||||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { takeSnapshot } = useContext(undoRedoContext);
|
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
|
||||||
const { nodesOnFlow, setNodesOnFlow } = useContext(FlowsContext);
|
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 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 position = useRef({ x: 0, y: 0 });
|
const position = useRef({ x: 0, y: 0 });
|
||||||
const [lastSelection, setLastSelection] =
|
const [lastSelection, setLastSelection] =
|
||||||
useState<OnSelectionChangeParams | null>(null);
|
useState<OnSelectionChangeParams | null>(null);
|
||||||
|
|
||||||
const saveCurrentFlowTimeout = () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
saveCurrentFlow();
|
|
||||||
}, 500); // need to do this because ReactFlow is not asynchronous.
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
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 (
|
if (
|
||||||
!isWrappedWithClass(event, "nocopy") &&
|
!isWrappedWithClass(event, "nocopy") &&
|
||||||
window.getSelection()?.toString().length === 0
|
window.getSelection()?.toString().length === 0
|
||||||
|
|
@ -107,8 +107,7 @@ export default function Page({
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setLastCopiedSelection(_.cloneDeep(lastSelection));
|
setLastCopiedSelection(_.cloneDeep(lastSelection));
|
||||||
}
|
} else if (
|
||||||
if (
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
(event.ctrlKey || event.metaKey) &&
|
||||||
event.key === "v" &&
|
event.key === "v" &&
|
||||||
lastCopiedSelection
|
lastCopiedSelection
|
||||||
|
|
@ -119,8 +118,7 @@ export default function Page({
|
||||||
x: position.current.x,
|
x: position.current.x,
|
||||||
y: position.current.y,
|
y: position.current.y,
|
||||||
});
|
});
|
||||||
}
|
} else if (
|
||||||
if (
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
(event.ctrlKey || event.metaKey) &&
|
||||||
event.key === "g" &&
|
event.key === "g" &&
|
||||||
lastSelection
|
lastSelection
|
||||||
|
|
@ -137,7 +135,6 @@ export default function Page({
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
deleteNode(lastSelection.nodes.map((node) => node.id));
|
deleteNode(lastSelection.nodes.map((node) => node.id));
|
||||||
deleteEdge(lastSelection.edges.map((edge) => edge.id));
|
deleteEdge(lastSelection.edges.map((edge) => edge.id));
|
||||||
saveCurrentFlowTimeout();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -153,148 +150,32 @@ export default function Page({
|
||||||
document.removeEventListener("keydown", onKeyDown);
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
document.removeEventListener("mousemove", handleMouseMove);
|
document.removeEventListener("mousemove", handleMouseMove);
|
||||||
};
|
};
|
||||||
}, [
|
}, [lastCopiedSelection, lastSelection, takeSnapshot]);
|
||||||
lastCopiedSelection,
|
|
||||||
lastSelection,
|
|
||||||
takeSnapshot,
|
|
||||||
saveCurrentFlowTimeout,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
|
const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
|
||||||
|
|
||||||
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const { setErrorData } = useContext(alertContext);
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(
|
|
||||||
flow.data?.nodes ?? []
|
|
||||||
);
|
|
||||||
|
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(
|
|
||||||
flow.data?.edges ?? []
|
|
||||||
);
|
|
||||||
const { setViewport } = useReactFlow();
|
|
||||||
const edgeUpdateSuccessful = useRef(true);
|
const edgeUpdateSuccessful = useRef(true);
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||||
|
|
||||||
const timeoutRef = useRef<NodeJS.Timeout>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
if (reactFlowInstance) {
|
||||||
setNodes(flow?.data?.nodes ?? []);
|
resetFlow({
|
||||||
setEdges(flow?.data?.edges ?? []);
|
nodes: flow?.data?.nodes ?? [],
|
||||||
setViewport(flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 });
|
edges: flow?.data?.edges ?? [],
|
||||||
|
viewport: 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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
saveCurrentFlowTimeout();
|
}
|
||||||
},
|
}, [currentFlowId, reactFlowInstance]);
|
||||||
[onEdgesChange, setNodes, setTabsState, saveCurrentFlowTimeout, tabId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onNodesChangeMod = useCallback(
|
const onConnectMod = 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]
|
|
||||||
);
|
|
||||||
|
|
||||||
function updateNodeFlow(changeString: string) {
|
|
||||||
setNodesOnFlow(changeString);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onConnect = useCallback(
|
|
||||||
(params: Connection) => {
|
(params: Connection) => {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
setEdges((eds) =>
|
onConnect(params);
|
||||||
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();
|
|
||||||
},
|
},
|
||||||
[setEdges, setNodes, takeSnapshot, addEdge]
|
[setEdges, takeSnapshot, addEdge]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onNodeDragStart: NodeDragHandler = useCallback(() => {
|
const onNodeDragStart: NodeDragHandler = useCallback(() => {
|
||||||
|
|
@ -303,6 +184,16 @@ export default function Page({
|
||||||
// 👉 you can place your event handlers here
|
// 👉 you can place your event handlers here
|
||||||
}, [takeSnapshot]);
|
}, [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(() => {
|
const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
|
||||||
// 👇 make dragging a selection undoable
|
// 👇 make dragging a selection undoable
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
|
|
@ -317,58 +208,65 @@ export default function Page({
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onConnect = useCallback(
|
||||||
|
(connection: Connection) => {
|
||||||
|
const 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",
|
||||||
|
},
|
||||||
|
edges
|
||||||
|
);
|
||||||
|
setEdges(newEdges);
|
||||||
|
useFlowsManagerStore
|
||||||
|
.getState()
|
||||||
|
.autoSaveCurrentFlow(
|
||||||
|
nodes,
|
||||||
|
newEdges,
|
||||||
|
reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[nodes, edges, setEdges, reactFlowInstance, addEdge]
|
||||||
|
);
|
||||||
|
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
(event: React.DragEvent) => {
|
(event: React.DragEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (event.dataTransfer.types.some((types) => types === "nodedata")) {
|
if (event.dataTransfer.types.some((types) => types === "nodedata")) {
|
||||||
takeSnapshot();
|
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
|
// 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")
|
event.dataTransfer.getData("nodedata")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Calculate the position where the node should be created
|
const newId = getNodeId(data.type);
|
||||||
const position = reactFlowInstance!.screenToFlowPosition({
|
|
||||||
x: event.clientX,
|
|
||||||
y: event.clientY,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Generate a unique node ID
|
const newNode: NodeType = {
|
||||||
let { type } = data;
|
id: newId,
|
||||||
let newId = getNodeId(type);
|
type: "genericNode",
|
||||||
let newNode: NodeType;
|
position: { x: 0, y: 0 },
|
||||||
|
data: {
|
||||||
if (data.type !== "groupNode") {
|
...data,
|
||||||
// Create a new node object
|
|
||||||
newNode = {
|
|
||||||
id: newId,
|
id: newId,
|
||||||
type: "genericNode",
|
},
|
||||||
position,
|
};
|
||||||
data: {
|
paste(
|
||||||
...data,
|
{ nodes: [newNode], edges: [] },
|
||||||
id: newId,
|
{ x: event.clientX, y: event.clientY }
|
||||||
},
|
);
|
||||||
};
|
|
||||||
} 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));
|
|
||||||
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
|
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
if (event.dataTransfer.files.item(0)!.type === "application/json") {
|
if (event.dataTransfer.files.item(0)!.type === "application/json") {
|
||||||
|
|
@ -396,32 +294,21 @@ export default function Page({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Specify dependencies for useCallback
|
// 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(() => {
|
const onEdgeUpdateStart = useCallback(() => {
|
||||||
edgeUpdateSuccessful.current = false;
|
edgeUpdateSuccessful.current = false;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onEdgeUpdate = useCallback(
|
const onEdgeUpdate = useCallback(
|
||||||
(oldEdge: Edge, newConnection: Connection) => {
|
(oldEdge: Edge, newConnection: Connection) => {
|
||||||
if (isValidConnection(newConnection, reactFlowInstance!)) {
|
if (isValidConnection(newConnection, nodes, edges)) {
|
||||||
edgeUpdateSuccessful.current = true;
|
edgeUpdateSuccessful.current = true;
|
||||||
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[reactFlowInstance, setEdges]
|
[setEdges]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onEdgeUpdateEnd = useCallback((_, edge: Edge): void => {
|
const onEdgeUpdateEnd = useCallback((_, edge: Edge): void => {
|
||||||
|
|
@ -461,20 +348,6 @@ export default function Page({
|
||||||
setFilterEdge([]);
|
setFilterEdge([]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onMove = useCallback(() => {
|
|
||||||
saveCurrentFlowTimeout();
|
|
||||||
//@ts-ignore
|
|
||||||
setTabsState((prev: FlowsState) => {
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
[tabId]: {
|
|
||||||
...prev[tabId],
|
|
||||||
isPending: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [setTabsState, saveCurrentFlowTimeout]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full overflow-hidden">
|
<div className="flex h-full overflow-hidden">
|
||||||
{!view && <ExtraSidebar />}
|
{!view && <ExtraSidebar />}
|
||||||
|
|
@ -486,21 +359,12 @@ export default function Page({
|
||||||
{Object.keys(templates).length > 0 &&
|
{Object.keys(templates).length > 0 &&
|
||||||
Object.keys(types).length > 0 ? (
|
Object.keys(types).length > 0 ? (
|
||||||
<div id="react-flow-id" className="h-full w-full">
|
<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
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
onMove={onMove}
|
|
||||||
edges={edges}
|
edges={edges}
|
||||||
onNodesChange={onNodesChangeMod}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChangeMod}
|
onEdgesChange={onEdgesChange}
|
||||||
onConnect={onConnect}
|
onConnect={onConnectMod}
|
||||||
disableKeyboardA11y={true}
|
disableKeyboardA11y={true}
|
||||||
onInit={setReactFlowInstance}
|
onInit={setReactFlowInstance}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
|
|
@ -508,11 +372,13 @@ export default function Page({
|
||||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||||
onNodeDragStart={onNodeDragStart}
|
onNodeDragStart={onNodeDragStart}
|
||||||
|
onNodeDragStop={onNodeDragStop}
|
||||||
onSelectionDragStart={onSelectionDragStart}
|
onSelectionDragStart={onSelectionDragStart}
|
||||||
onSelectionEnd={onSelectionEnd}
|
onSelectionEnd={onSelectionEnd}
|
||||||
onSelectionStart={onSelectionStart}
|
onSelectionStart={onSelectionStart}
|
||||||
connectionLineComponent={ConnectionLineComponent}
|
connectionLineComponent={ConnectionLineComponent}
|
||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
|
onMoveEnd={onMoveEnd}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
onSelectionChange={onSelectionChange}
|
onSelectionChange={onSelectionChange}
|
||||||
deleteKeyCode={[]}
|
deleteKeyCode={[]}
|
||||||
|
|
@ -542,7 +408,8 @@ export default function Page({
|
||||||
) {
|
) {
|
||||||
const { newFlow } = generateFlow(
|
const { newFlow } = generateFlow(
|
||||||
lastSelection!,
|
lastSelection!,
|
||||||
reactFlowInstance!,
|
nodes,
|
||||||
|
edges,
|
||||||
getRandomName()
|
getRandomName()
|
||||||
);
|
);
|
||||||
const newGroupNode = generateNodeFromFlow(
|
const newGroupNode = generateNodeFromFlow(
|
||||||
|
|
@ -578,9 +445,7 @@ export default function Page({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
{!view && (
|
{!view && <Chat flow={flow} />}
|
||||||
<Chat flow={flow} reactFlowInstance={reactFlowInstance!} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { useContext, useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
||||||
import IconComponent from "../../../../components/genericIconComponent";
|
import IconComponent from "../../../../components/genericIconComponent";
|
||||||
import { Input } from "../../../../components/ui/input";
|
import { Input } from "../../../../components/ui/input";
|
||||||
import { Separator } from "../../../../components/ui/separator";
|
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 ApiModal from "../../../../modals/ApiModal";
|
||||||
import ExportModal from "../../../../modals/exportModal";
|
import ExportModal from "../../../../modals/exportModal";
|
||||||
import ShareModal from "../../../../modals/shareModal";
|
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 { APIClassType, APIObjectType } from "../../../../types/api";
|
||||||
import {
|
import {
|
||||||
nodeColors,
|
nodeColors,
|
||||||
|
|
@ -26,15 +27,20 @@ import DisclosureComponent from "../DisclosureComponent";
|
||||||
import SidebarDraggableComponent from "./sideBarDraggableComponent";
|
import SidebarDraggableComponent from "./sideBarDraggableComponent";
|
||||||
|
|
||||||
export default function ExtraSidebar(): JSX.Element {
|
export default function ExtraSidebar(): JSX.Element {
|
||||||
const { data, templates, getFilterEdge, setFilterEdge, reactFlowInstance } =
|
const data = useTypesStore((state) => state.data);
|
||||||
useContext(typesContext);
|
const templates = useTypesStore((state) => state.templates);
|
||||||
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, version } =
|
const getFilterEdge = useTypesStore((state) => state.getFilterEdge);
|
||||||
useContext(FlowsContext);
|
const setFilterEdge = useTypesStore((state) => state.setFilterEdge);
|
||||||
const { hasApiKey, validApiKey, hasStore } = useContext(StoreContext);
|
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||||
const { setErrorData } = useContext(alertContext);
|
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 [dataFilter, setFilterData] = useState(data);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const isPending = tabsState[tabId]?.isPending;
|
|
||||||
function onDragStart(
|
function onDragStart(
|
||||||
event: React.DragEvent<any>,
|
event: React.DragEvent<any>,
|
||||||
data: { type: string; node?: APIClassType }
|
data: { type: string; node?: APIClassType }
|
||||||
|
|
@ -72,7 +78,7 @@ export default function ExtraSidebar(): JSX.Element {
|
||||||
return ret;
|
return ret;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const flow = flows.find((flow) => flow.id === tabId);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// show components with error on load
|
// show components with error on load
|
||||||
let errors: string[] = [];
|
let errors: string[] = [];
|
||||||
|
|
@ -187,7 +193,7 @@ export default function ExtraSidebar(): JSX.Element {
|
||||||
() => (
|
() => (
|
||||||
<ShareModal
|
<ShareModal
|
||||||
is_component={false}
|
is_component={false}
|
||||||
component={flow!}
|
component={currentFlow!}
|
||||||
disabled={!hasApiKey || !validApiKey || !hasStore}
|
disabled={!hasApiKey || !validApiKey || !hasStore}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
|
@ -212,17 +218,15 @@ export default function ExtraSidebar(): JSX.Element {
|
||||||
</button>
|
</button>
|
||||||
</ShareModal>
|
</ShareModal>
|
||||||
),
|
),
|
||||||
[hasApiKey, validApiKey, flow, hasStore]
|
[hasApiKey, validApiKey, currentFlow, hasStore]
|
||||||
);
|
);
|
||||||
|
|
||||||
const ExportMemo = useMemo(
|
const ExportMemo = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<ExportModal>
|
<ExportModal>
|
||||||
<ShadTooltip content="Export" side="top">
|
<button className={classNames("extra-side-bar-buttons")}>
|
||||||
<button className={classNames("extra-side-bar-buttons")}>
|
<IconComponent name="FileDown" className="side-bar-button-size" />
|
||||||
<IconComponent name="FileDown" className="side-bar-button-size" />
|
</button>
|
||||||
</button>
|
|
||||||
</ShadTooltip>
|
|
||||||
</ExportModal>
|
</ExportModal>
|
||||||
),
|
),
|
||||||
[]
|
[]
|
||||||
|
|
@ -263,11 +267,19 @@ export default function ExtraSidebar(): JSX.Element {
|
||||||
</button>
|
</button>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
</div>
|
</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">
|
<ShadTooltip content={"Code"} side="top">
|
||||||
<div className="side-bar-button">
|
<div className="side-bar-button">
|
||||||
{flow && flow.data && (
|
{currentFlow && currentFlow.data && (
|
||||||
<ApiModal flow={flow}>
|
<ApiModal flow={currentFlow}>
|
||||||
<button
|
<button
|
||||||
className={"w-full " + (!isBuilt ? "button-disable" : "")}
|
className={"w-full " + (!isBuilt ? "button-disable" : "")}
|
||||||
>
|
>
|
||||||
|
|
@ -285,34 +297,6 @@ export default function ExtraSidebar(): JSX.Element {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ShadTooltip>
|
</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>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className="side-bar-search-div-placement">
|
<div className="side-bar-search-div-placement">
|
||||||
|
|
|
||||||
|
|
@ -1,152 +1,160 @@
|
||||||
import { DragEventHandler, useContext, useRef, useState } from "react";
|
import { DragEventHandler, forwardRef, useRef, useState } from "react";
|
||||||
import IconComponent from "../../../../../components/genericIconComponent";
|
import IconComponent from "../../../../../components/genericIconComponent";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
|
||||||
} from "../../../../../components/ui/select-custom";
|
} from "../../../../../components/ui/select-custom";
|
||||||
import { AuthContext } from "../../../../../contexts/authContext";
|
import { useDarkStore } from "../../../../../stores/darkStore";
|
||||||
import { FlowsContext } from "../../../../../contexts/flowsContext";
|
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
|
||||||
import { APIClassType } from "../../../../../types/api";
|
import { APIClassType } from "../../../../../types/api";
|
||||||
import {
|
import {
|
||||||
createFlowComponent,
|
createFlowComponent,
|
||||||
downloadNode,
|
downloadNode,
|
||||||
|
getNodeId,
|
||||||
} from "../../../../../utils/reactflowUtils";
|
} from "../../../../../utils/reactflowUtils";
|
||||||
import { removeCountFromString } from "../../../../../utils/utils";
|
import { removeCountFromString } from "../../../../../utils/utils";
|
||||||
|
|
||||||
export default function SidebarDraggableComponent({
|
export const SidebarDraggableComponent = forwardRef(
|
||||||
sectionName,
|
(
|
||||||
display_name,
|
{
|
||||||
itemName,
|
sectionName,
|
||||||
error,
|
display_name,
|
||||||
color,
|
itemName,
|
||||||
onDragStart,
|
error,
|
||||||
apiClass,
|
color,
|
||||||
official,
|
onDragStart,
|
||||||
}: {
|
apiClass,
|
||||||
sectionName: string;
|
official,
|
||||||
apiClass: APIClassType;
|
}: {
|
||||||
display_name: string;
|
sectionName: string;
|
||||||
itemName: string;
|
apiClass: APIClassType;
|
||||||
error: boolean;
|
display_name: string;
|
||||||
color: string;
|
itemName: string;
|
||||||
onDragStart: DragEventHandler<HTMLDivElement>;
|
error: boolean;
|
||||||
official: boolean;
|
color: string;
|
||||||
}) {
|
onDragStart: DragEventHandler<HTMLDivElement>;
|
||||||
const [open, setOpen] = useState(false);
|
official: boolean;
|
||||||
const { getNodeId, deleteComponent, version } = useContext(FlowsContext);
|
},
|
||||||
const { autoLogin, userData } = useContext(AuthContext);
|
ref
|
||||||
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
|
) => {
|
||||||
const popoverRef = useRef<HTMLDivElement>(null);
|
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) => {
|
const handlePointerDown = (e) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
const rect = popoverRef.current?.getBoundingClientRect() ?? {
|
const rect = popoverRef.current?.getBoundingClientRect() ?? {
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
};
|
};
|
||||||
setCursorPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
setCursorPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleSelectChange(value: string) {
|
function handleSelectChange(value: string) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case "share":
|
case "share":
|
||||||
break;
|
break;
|
||||||
case "download":
|
case "download":
|
||||||
const type = removeCountFromString(itemName);
|
const type = removeCountFromString(itemName);
|
||||||
downloadNode(
|
downloadNode(
|
||||||
createFlowComponent(
|
createFlowComponent(
|
||||||
{ id: getNodeId(type), type, node: apiClass },
|
{ id: getNodeId(type), type, node: apiClass },
|
||||||
version
|
version
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "delete":
|
case "delete":
|
||||||
deleteComponent(display_name);
|
deleteComponent(display_name);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return (
|
||||||
return (
|
<Select
|
||||||
<Select
|
onValueChange={handleSelectChange}
|
||||||
onValueChange={handleSelectChange}
|
onOpenChange={(change) => setOpen(change)}
|
||||||
onOpenChange={(change) => setOpen(change)}
|
open={open}
|
||||||
open={open}
|
|
||||||
key={itemName}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
onPointerDown={handlePointerDown}
|
|
||||||
onContextMenuCapture={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setOpen(true);
|
|
||||||
}}
|
|
||||||
key={itemName}
|
key={itemName}
|
||||||
data-tooltip-id={itemName}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
draggable={!error}
|
onPointerDown={handlePointerDown}
|
||||||
className={
|
onContextMenuCapture={(e) => {
|
||||||
"side-bar-components-border bg-background" +
|
e.preventDefault();
|
||||||
(error ? " cursor-not-allowed select-none" : "")
|
setOpen(true);
|
||||||
}
|
|
||||||
style={{
|
|
||||||
borderLeftColor: color,
|
|
||||||
}}
|
|
||||||
onDragStart={onDragStart}
|
|
||||||
onDragEnd={() => {
|
|
||||||
document.body.removeChild(
|
|
||||||
document.getElementsByClassName("cursor-grabbing")[0]
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
|
key={itemName}
|
||||||
|
data-tooltip-id={itemName}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-testid={sectionName + display_name}
|
draggable={!error}
|
||||||
id={sectionName + display_name}
|
className={
|
||||||
className="side-bar-components-div-form"
|
"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
|
||||||
<div ref={popoverRef}>
|
data-testid={sectionName + display_name}
|
||||||
<IconComponent
|
id={sectionName + display_name}
|
||||||
name="Menu"
|
className="side-bar-components-div-form"
|
||||||
className="side-bar-components-icon "
|
>
|
||||||
/>
|
<span className="side-bar-components-text">{display_name}</span>
|
||||||
<SelectTrigger></SelectTrigger>
|
<div ref={popoverRef}>
|
||||||
<SelectContent
|
<IconComponent
|
||||||
position="popper"
|
name="Menu"
|
||||||
side="bottom"
|
className="side-bar-components-icon "
|
||||||
sideOffset={-25}
|
/>
|
||||||
style={{
|
<SelectContent
|
||||||
position: "absolute",
|
position="popper"
|
||||||
left: cursorPos.x,
|
side="bottom"
|
||||||
top: cursorPos.y,
|
sideOffset={-25}
|
||||||
}}
|
style={{
|
||||||
>
|
position: "absolute",
|
||||||
<SelectItem value={"download"}>
|
left: cursorPos.x,
|
||||||
<div className="flex">
|
top: cursorPos.y,
|
||||||
<IconComponent
|
}}
|
||||||
name="Download"
|
>
|
||||||
className="relative top-0.5 mr-2 h-4 w-4"
|
<SelectItem value={"download"}>
|
||||||
/>{" "}
|
|
||||||
Download{" "}
|
|
||||||
</div>{" "}
|
|
||||||
</SelectItem>
|
|
||||||
{!official && (
|
|
||||||
<SelectItem value={"delete"}>
|
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Trash2"
|
name="Download"
|
||||||
className="relative top-0.5 mr-2 h-4 w-4"
|
className="relative top-0.5 mr-2 h-4 w-4"
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Delete{" "}
|
Download{" "}
|
||||||
</div>{" "}
|
</div>{" "}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
{!official && (
|
||||||
</SelectContent>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Select>
|
||||||
</Select>
|
);
|
||||||
);
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
|
export default SidebarDraggableComponent;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useReactFlow, useUpdateNodeInternals } from "reactflow";
|
|
||||||
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
||||||
import IconComponent from "../../../../components/genericIconComponent";
|
import IconComponent from "../../../../components/genericIconComponent";
|
||||||
import {
|
import {
|
||||||
|
|
@ -9,12 +8,13 @@ import {
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "../../../../components/ui/select-custom";
|
} 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 ConfirmationModal from "../../../../modals/ConfirmationModal";
|
||||||
import EditNodeModal from "../../../../modals/EditNodeModal";
|
import EditNodeModal from "../../../../modals/EditNodeModal";
|
||||||
import ShareModal from "../../../../modals/shareModal";
|
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 { nodeToolbarPropsType } from "../../../../types/components";
|
||||||
import { FlowType } from "../../../../types/flow";
|
import { FlowType } from "../../../../types/flow";
|
||||||
import {
|
import {
|
||||||
|
|
@ -33,26 +33,25 @@ export default function NodeToolbarComponent({
|
||||||
numberOfHandles,
|
numberOfHandles,
|
||||||
showNode,
|
showNode,
|
||||||
}: nodeToolbarPropsType): JSX.Element {
|
}: nodeToolbarPropsType): JSX.Element {
|
||||||
const [nodeLength, setNodeLength] = useState(
|
const nodeLength = Object.keys(data.node!.template).filter(
|
||||||
Object.keys(data.node!.template).filter(
|
(templateField) =>
|
||||||
(templateField) =>
|
templateField.charAt(0) !== "_" &&
|
||||||
templateField.charAt(0) !== "_" &&
|
data.node?.template[templateField].show &&
|
||||||
data.node?.template[templateField].show &&
|
(data.node.template[templateField].type === "str" ||
|
||||||
(data.node.template[templateField].type === "str" ||
|
data.node.template[templateField].type === "bool" ||
|
||||||
data.node.template[templateField].type === "bool" ||
|
data.node.template[templateField].type === "float" ||
|
||||||
data.node.template[templateField].type === "float" ||
|
data.node.template[templateField].type === "code" ||
|
||||||
data.node.template[templateField].type === "code" ||
|
data.node.template[templateField].type === "prompt" ||
|
||||||
data.node.template[templateField].type === "prompt" ||
|
data.node.template[templateField].type === "file" ||
|
||||||
data.node.template[templateField].type === "file" ||
|
data.node.template[templateField].type === "Any" ||
|
||||||
data.node.template[templateField].type === "Any" ||
|
data.node.template[templateField].type === "int" ||
|
||||||
data.node.template[templateField].type === "int" ||
|
data.node.template[templateField].type === "dict" ||
|
||||||
data.node.template[templateField].type === "dict" ||
|
data.node.template[templateField].type === "NestedDict")
|
||||||
data.node.template[templateField].type === "NestedDict")
|
).length;
|
||||||
).length
|
|
||||||
);
|
const hasStore = useStoreStore((state) => state.hasStore);
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||||
const { getNodeId } = useContext(FlowsContext);
|
const validApiKey = useStoreStore((state) => state.validApiKey);
|
||||||
const { hasApiKey, validApiKey, hasStore } = useContext(StoreContext);
|
|
||||||
|
|
||||||
function canMinimize() {
|
function canMinimize() {
|
||||||
let countHandles: number = 0;
|
let countHandles: number = 0;
|
||||||
|
|
@ -65,9 +64,16 @@ export default function NodeToolbarComponent({
|
||||||
const isMinimal = canMinimize();
|
const isMinimal = canMinimize();
|
||||||
const isGroup = data.node?.flow ? true : false;
|
const isGroup = data.node?.flow ? true : false;
|
||||||
|
|
||||||
const { paste, saveComponent, version, flows } = useContext(FlowsContext);
|
const paste = useFlowStore((state) => state.paste);
|
||||||
const { takeSnapshot } = useContext(undoRedoContext);
|
const nodes = useFlowStore((state) => state.nodes);
|
||||||
const reactFlowInstance = useReactFlow();
|
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 [showModalAdvanced, setShowModalAdvanced] = useState(false);
|
||||||
const [showconfirmShare, setShowconfirmShare] = useState(false);
|
const [showconfirmShare, setShowconfirmShare] = useState(false);
|
||||||
const [selectedValue, setSelectedValue] = useState("");
|
const [selectedValue, setSelectedValue] = useState("");
|
||||||
|
|
@ -99,7 +105,6 @@ export default function NodeToolbarComponent({
|
||||||
case "show":
|
case "show":
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
setShowNode(data.showNode ?? true ? false : true);
|
setShowNode(data.showNode ?? true ? false : true);
|
||||||
updateNodeInternals(data.id);
|
|
||||||
break;
|
break;
|
||||||
case "Download":
|
case "Download":
|
||||||
downloadNode(createFlowComponent(cloneDeep(data), version));
|
downloadNode(createFlowComponent(cloneDeep(data), version));
|
||||||
|
|
@ -115,7 +120,7 @@ export default function NodeToolbarComponent({
|
||||||
case "ungroup":
|
case "ungroup":
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
updateFlowPosition(position, data.node?.flow!);
|
updateFlowPosition(position, data.node?.flow!);
|
||||||
expandGroupNode(data, reactFlowInstance, getNodeId);
|
expandGroupNode(data, nodes, edges, setNodes, setEdges);
|
||||||
break;
|
break;
|
||||||
case "override":
|
case "override":
|
||||||
setShowOverrideModal(true);
|
setShowOverrideModal(true);
|
||||||
|
|
@ -152,14 +157,16 @@ export default function NodeToolbarComponent({
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
paste(
|
paste(
|
||||||
{
|
{
|
||||||
nodes: [reactFlowInstance.getNode(data.id)],
|
nodes: [nodes.find((node) => node.id === data.id)!],
|
||||||
edges: [],
|
edges: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
x: 50,
|
x: 50,
|
||||||
y: 10,
|
y: 10,
|
||||||
paneX: reactFlowInstance.getNode(data.id)?.position.x,
|
paneX: nodes.find((node) => node.id === data.id)?.position
|
||||||
paneY: reactFlowInstance.getNode(data.id)?.position.y,
|
.x,
|
||||||
|
paneY: nodes.find((node) => node.id === data.id)?.position
|
||||||
|
.y,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|
@ -287,7 +294,6 @@ export default function NodeToolbarComponent({
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
asChild
|
|
||||||
open={showOverrideModal}
|
open={showOverrideModal}
|
||||||
title={`Replace`}
|
title={`Replace`}
|
||||||
cancelText="Create New"
|
cancelText="Create New"
|
||||||
|
|
@ -307,9 +313,6 @@ export default function NodeToolbarComponent({
|
||||||
to replace it with the current or create a new one?
|
to replace it with the current or create a new one?
|
||||||
</span>
|
</span>
|
||||||
</ConfirmationModal.Content>
|
</ConfirmationModal.Content>
|
||||||
<ConfirmationModal.Trigger>
|
|
||||||
<></>
|
|
||||||
</ConfirmationModal.Trigger>
|
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
<EditNodeModal
|
<EditNodeModal
|
||||||
data={data}
|
data={data}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,28 @@
|
||||||
import { useContext, useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import Header from "../../components/headerComponent";
|
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";
|
import Page from "./components/PageComponent";
|
||||||
|
|
||||||
export default function FlowPage(): JSX.Element {
|
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();
|
const { id } = useParams();
|
||||||
|
|
||||||
// Set flow tab id
|
// Set flow tab id
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTabId(id!);
|
setCurrentFlowId(id!);
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header />
|
<Header />
|
||||||
<div className="flow-page-positioning">
|
<div className="flow-page-positioning">
|
||||||
{flows.length > 0 &&
|
{currentFlow && <Page flow={currentFlow} />}
|
||||||
tabId !== "" &&
|
|
||||||
flows.findIndex((flow) => flow.id === tabId) !== -1 && (
|
|
||||||
<Page flow={flows.find((flow) => flow.id === tabId)!} />
|
|
||||||
)}
|
|
||||||
<a
|
<a
|
||||||
target={"_blank"}
|
target={"_blank"}
|
||||||
href="https://logspace.ai/"
|
href="https://logspace.ai/"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import PaginatorComponent from "../../../../components/PaginatorComponent";
|
import PaginatorComponent from "../../../../components/PaginatorComponent";
|
||||||
import CollectionCardComponent from "../../../../components/cardComponent";
|
import CollectionCardComponent from "../../../../components/cardComponent";
|
||||||
|
|
@ -6,8 +6,8 @@ import CardsWrapComponent from "../../../../components/cardsWrapComponent";
|
||||||
import IconComponent from "../../../../components/genericIconComponent";
|
import IconComponent from "../../../../components/genericIconComponent";
|
||||||
import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent";
|
import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent";
|
||||||
import { Button } from "../../../../components/ui/button";
|
import { Button } from "../../../../components/ui/button";
|
||||||
import { alertContext } from "../../../../contexts/alertContext";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import { FlowsContext } from "../../../../contexts/flowsContext";
|
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||||
import { FlowType } from "../../../../types/flow";
|
import { FlowType } from "../../../../types/flow";
|
||||||
|
|
||||||
export default function ComponentsComponent({
|
export default function ComponentsComponent({
|
||||||
|
|
@ -15,9 +15,13 @@ export default function ComponentsComponent({
|
||||||
}: {
|
}: {
|
||||||
is_component?: boolean;
|
is_component?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { flows, removeFlow, uploadFlow, addFlow, isLoading } =
|
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||||
useContext(FlowsContext);
|
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
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 [pageSize, setPageSize] = useState(10);
|
||||||
const [pageIndex, setPageIndex] = useState(1);
|
const [pageIndex, setPageIndex] = useState(1);
|
||||||
const [loadingScreen, setLoadingScreen] = useState(true);
|
const [loadingScreen, setLoadingScreen] = useState(true);
|
||||||
|
|
@ -48,7 +52,7 @@ export default function ComponentsComponent({
|
||||||
const start = (pageIndex - 1) * pageSize;
|
const start = (pageIndex - 1) * pageSize;
|
||||||
const end = start + pageSize;
|
const end = start + pageSize;
|
||||||
setData(all.slice(start, end));
|
setData(all.slice(start, end));
|
||||||
}, [flows, pageIndex, pageSize]);
|
}, [flows, isLoading, pageIndex, pageSize]);
|
||||||
|
|
||||||
const [data, setData] = useState<FlowType[]>([]);
|
const [data, setData] = useState<FlowType[]>([]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Group, ToyBrick } from "lucide-react";
|
import { Group, ToyBrick } from "lucide-react";
|
||||||
import { useContext, useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||||
import DropdownButton from "../../components/DropdownButtonComponent";
|
import DropdownButton from "../../components/DropdownButtonComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
|
|
@ -7,12 +7,18 @@ import PageLayout from "../../components/pageLayout";
|
||||||
import SidebarNav from "../../components/sidebarComponent";
|
import SidebarNav from "../../components/sidebarComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
|
import { downloadFlows } from "../../utils/reactflowUtils";
|
||||||
export default function HomePage(): JSX.Element {
|
export default function HomePage(): JSX.Element {
|
||||||
const { setTabId, downloadFlows, uploadFlows, addFlow, uploadFlow } =
|
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||||
useContext(FlowsContext);
|
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
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 location = useLocation();
|
||||||
const pathname = location.pathname;
|
const pathname = location.pathname;
|
||||||
const is_component = pathname === "/components";
|
const is_component = pathname === "/components";
|
||||||
|
|
@ -56,7 +62,7 @@ export default function HomePage(): JSX.Element {
|
||||||
|
|
||||||
// Set a null id
|
// Set a null id
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTabId("");
|
setCurrentFlowId("");
|
||||||
}, [pathname]);
|
}, [pathname]);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,19 @@ import Header from "../../components/headerComponent";
|
||||||
import InputComponent from "../../components/inputComponent";
|
import InputComponent from "../../components/inputComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
|
import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
|
||||||
import { resetPassword, updateUser } from "../../controllers/API";
|
import { resetPassword, updateUser } from "../../controllers/API";
|
||||||
|
import useAlertStore from "../../stores/alertStore";
|
||||||
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import {
|
import {
|
||||||
inputHandlerEventType,
|
inputHandlerEventType,
|
||||||
patchUserInputStateType,
|
patchUserInputStateType,
|
||||||
} from "../../types/components";
|
} from "../../types/components";
|
||||||
import { gradients } from "../../utils/styleUtils";
|
import { gradients } from "../../utils/styleUtils";
|
||||||
export default function ProfileSettingsPage(): JSX.Element {
|
export default function ProfileSettingsPage(): JSX.Element {
|
||||||
const { setTabId } = useContext(FlowsContext);
|
const setCurrentFlowId = useFlowsManagerStore(
|
||||||
|
(state) => state.setCurrentFlowId
|
||||||
|
);
|
||||||
|
|
||||||
const [inputState, setInputState] = useState<patchUserInputStateType>(
|
const [inputState, setInputState] = useState<patchUserInputStateType>(
|
||||||
CONTROL_PATCH_USER_STATE
|
CONTROL_PATCH_USER_STATE
|
||||||
|
|
@ -25,9 +27,10 @@ export default function ProfileSettingsPage(): JSX.Element {
|
||||||
|
|
||||||
// set null id
|
// set null id
|
||||||
useEffect(() => {
|
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 { userData, setUserData } = useContext(AuthContext);
|
||||||
const { password, cnfPassword, gradient } = inputState;
|
const { password, cnfPassword, gradient } = inputState;
|
||||||
|
|
||||||
|
|
@ -97,6 +100,7 @@ export default function ProfileSettingsPage(): JSX.Element {
|
||||||
Password{" "}
|
Password{" "}
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
<InputComponent
|
<InputComponent
|
||||||
|
id="pasword"
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
handleInput({ target: { name: "password", value } });
|
handleInput({ target: { name: "password", value } });
|
||||||
}}
|
}}
|
||||||
|
|
@ -118,6 +122,7 @@ export default function ProfileSettingsPage(): JSX.Element {
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
|
|
||||||
<InputComponent
|
<InputComponent
|
||||||
|
id="cnfPassword"
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
handleInput({ target: { name: "cnfPassword", value } });
|
handleInput({ target: { name: "cnfPassword", value } });
|
||||||
}}
|
}}
|
||||||
|
|
@ -143,9 +148,9 @@ export default function ProfileSettingsPage(): JSX.Element {
|
||||||
<GradientChooserComponent
|
<GradientChooserComponent
|
||||||
value={
|
value={
|
||||||
gradient == ""
|
gradient == ""
|
||||||
? userData!.profile_image ??
|
? userData?.profile_image ??
|
||||||
gradients[
|
gradients[
|
||||||
parseInt(userData!.id ?? "", 30) % gradients.length
|
parseInt(userData?.id ?? "", 30) % gradients.length
|
||||||
]
|
]
|
||||||
: gradient
|
: gradient
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,34 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "../../components/ui/select";
|
} from "../../components/ui/select";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
import {
|
||||||
import { StoreContext } from "../../contexts/storeContext";
|
checkHasApiKey,
|
||||||
import { getStoreComponents, getStoreTags } from "../../controllers/API";
|
getStoreComponents,
|
||||||
|
getStoreTags,
|
||||||
|
} from "../../controllers/API";
|
||||||
import StoreApiKeyModal from "../../modals/StoreApiKeyModal";
|
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 { storeComponent } from "../../types/store";
|
||||||
import { cn } from "../../utils/utils";
|
import { cn } from "../../utils/utils";
|
||||||
|
|
||||||
export default function StorePage(): JSX.Element {
|
export default function StorePage(): JSX.Element {
|
||||||
const { validApiKey, setValidApiKey, hasApiKey, loadingApiKey } =
|
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||||
useContext(StoreContext);
|
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 { 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 [loading, setLoading] = useState(true);
|
||||||
const [loadingTags, setLoadingTags] = useState(true);
|
const [loadingTags, setLoadingTags] = useState(true);
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
@ -154,7 +168,7 @@ export default function StorePage(): JSX.Element {
|
||||||
|
|
||||||
// Set a null id
|
// Set a null id
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTabId("");
|
setCurrentFlowId("");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function resetPagination() {
|
function resetPagination() {
|
||||||
|
|
@ -162,6 +176,23 @@ export default function StorePage(): JSX.Element {
|
||||||
setPageSize(12);
|
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 (
|
return (
|
||||||
<PageLayout
|
<PageLayout
|
||||||
betaIcon
|
betaIcon
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
import { useContext, useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { FlowsContext } from "../../contexts/flowsContext";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import Page from "../FlowPage/components/PageComponent";
|
import Page from "../FlowPage/components/PageComponent";
|
||||||
|
|
||||||
export default function ViewPage() {
|
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();
|
const { id } = useParams();
|
||||||
|
|
||||||
// Set flow tab id
|
// Set flow tab id
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTabId(id!);
|
setCurrentFlowId(id!);
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flow-page-positioning">
|
<div className="flow-page-positioning">
|
||||||
{flows.length > 0 &&
|
{currentFlow && <Page view flow={currentFlow} />}
|
||||||
tabId !== "" &&
|
|
||||||
flows.findIndex((flow) => flow.id === tabId) !== -1 && (
|
|
||||||
<Page view flow={flows.find((flow) => flow.id === tabId)!} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import InputComponent from "../../components/inputComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Input } from "../../components/ui/input";
|
import { Input } from "../../components/ui/input";
|
||||||
import { CONTROL_LOGIN_STATE } from "../../constants/constants";
|
import { CONTROL_LOGIN_STATE } from "../../constants/constants";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
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 { LoginType } from "../../types/api";
|
||||||
import {
|
import {
|
||||||
inputHandlerEventType,
|
inputHandlerEventType,
|
||||||
|
|
@ -19,10 +19,10 @@ export default function LoginPage(): JSX.Element {
|
||||||
useState<loginInputStateType>(CONTROL_LOGIN_STATE);
|
useState<loginInputStateType>(CONTROL_LOGIN_STATE);
|
||||||
|
|
||||||
const { password, username } = inputState;
|
const { password, username } = inputState;
|
||||||
const { login, getAuthentication, setUserData, setIsAdmin } =
|
const { login, isAuthenticated, setUserData, setIsAdmin } =
|
||||||
useContext(AuthContext);
|
useContext(AuthContext);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { setErrorData } = useContext(alertContext);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
|
||||||
function handleInput({
|
function handleInput({
|
||||||
target: { name, value },
|
target: { name, value },
|
||||||
|
|
@ -37,8 +37,7 @@ export default function LoginPage(): JSX.Element {
|
||||||
};
|
};
|
||||||
onLogin(user)
|
onLogin(user)
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
login(user.access_token, user.refresh_token);
|
login(user.access_token);
|
||||||
getUser();
|
|
||||||
navigate("/");
|
navigate("/");
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.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 (
|
return (
|
||||||
<Form.Root
|
<Form.Root
|
||||||
onSubmit={(event) => {
|
onSubmit={(event) => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import * as Form from "@radix-ui/react-form";
|
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 { Link, useNavigate } from "react-router-dom";
|
||||||
import InputComponent from "../../components/inputComponent";
|
import InputComponent from "../../components/inputComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
|
|
@ -8,8 +8,8 @@ import {
|
||||||
CONTROL_INPUT_STATE,
|
CONTROL_INPUT_STATE,
|
||||||
SIGN_UP_SUCCESS,
|
SIGN_UP_SUCCESS,
|
||||||
} from "../../constants/constants";
|
} from "../../constants/constants";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { addUser } from "../../controllers/API";
|
import { addUser } from "../../controllers/API";
|
||||||
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import {
|
import {
|
||||||
UserInputType,
|
UserInputType,
|
||||||
inputHandlerEventType,
|
inputHandlerEventType,
|
||||||
|
|
@ -23,7 +23,8 @@ export default function SignUp(): JSX.Element {
|
||||||
const [isDisabled, setDisableBtn] = useState<boolean>(true);
|
const [isDisabled, setDisableBtn] = useState<boolean>(true);
|
||||||
|
|
||||||
const { password, cnfPassword, username } = inputState;
|
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();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
function handleInput({
|
function handleInput({
|
||||||
|
|
|
||||||
95
src/frontend/src/stores/alertStore.ts
Normal file
95
src/frontend/src/stores/alertStore.ts
Normal 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;
|
||||||
20
src/frontend/src/stores/darkStore.tsx
Normal file
20
src/frontend/src/stores/darkStore.tsx
Normal 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 }));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
252
src/frontend/src/stores/flowStore.ts
Normal file
252
src/frontend/src/stores/flowStore.ts
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
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,
|
||||||
|
isBuilt: false,
|
||||||
|
reactFlowInstance: null,
|
||||||
|
lastCopiedSelection: null,
|
||||||
|
|
||||||
|
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 });
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useFlowStore;
|
||||||
390
src/frontend/src/stores/flowsManagerStore.ts
Normal file
390
src/frontend/src/stores/flowsManagerStore.ts
Normal 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;
|
||||||
37
src/frontend/src/stores/storeStore.tsx
Normal file
37
src/frontend/src/stores/storeStore.tsx
Normal 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();
|
||||||
50
src/frontend/src/stores/typesStore.ts
Normal file
50
src/frontend/src/stores/typesStore.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
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: {},
|
||||||
|
getFilterEdge: [],
|
||||||
|
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 });
|
||||||
|
},
|
||||||
|
setFilterEdge: (newState) => {
|
||||||
|
set({ getFilterEdge: newState });
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
@ -2,8 +2,8 @@ import { Edge, Node, Viewport } from "reactflow";
|
||||||
import { FlowType } from "../flow";
|
import { FlowType } from "../flow";
|
||||||
//kind and class are just representative names to represent the actual structure of the object received by the API
|
//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 APIDataType = { [key: string]: APIKindType };
|
||||||
export type APIObjectType = { kind: APIKindType; [key: string]: APIKindType };
|
export type APIObjectType = { [key: string]: APIKindType };
|
||||||
export type APIKindType = { class: APIClassType; [key: string]: APIClassType };
|
export type APIKindType = { [key: string]: APIClassType };
|
||||||
export type APITemplateType = {
|
export type APITemplateType = {
|
||||||
[key: string]: TemplateVariableType;
|
[key: string]: TemplateVariableType;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { ReactFlowInstance } from "reactflow";
|
|
||||||
import { FlowType } from "../flow";
|
import { FlowType } from "../flow";
|
||||||
|
|
||||||
export type ChatType = { flow: FlowType; reactFlowInstance: ReactFlowInstance };
|
export type ChatType = { flow: FlowType };
|
||||||
export type ChatMessageType = {
|
export type ChatMessageType = {
|
||||||
message: string | Object;
|
message: string | Object;
|
||||||
template?: string;
|
template?: string;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { ReactFlowJsonObject, XYPosition } from "reactflow";
|
||||||
import { APIClassType, APITemplateType, TemplateVariableType } from "../api";
|
import { APIClassType, APITemplateType, TemplateVariableType } from "../api";
|
||||||
import { ChatMessageType } from "../chat";
|
import { ChatMessageType } from "../chat";
|
||||||
import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index";
|
import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index";
|
||||||
import { typesContextType } from "../typesContext";
|
|
||||||
import { sourceHandleType, targetHandleType } from "./../flow/index";
|
import { sourceHandleType, targetHandleType } from "./../flow/index";
|
||||||
export type InputComponentType = {
|
export type InputComponentType = {
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
|
|
@ -48,7 +47,6 @@ export type ParameterComponentType = {
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
name?: string;
|
name?: string;
|
||||||
tooltipTitle: string | undefined;
|
tooltipTitle: string | undefined;
|
||||||
dataContext?: typesContextType;
|
|
||||||
optionalHandle?: Array<String> | null;
|
optionalHandle?: Array<String> | null;
|
||||||
info?: string;
|
info?: string;
|
||||||
proxy?: { field: string; id: string };
|
proxy?: { field: string; id: string };
|
||||||
|
|
@ -264,12 +262,12 @@ export type LoadingComponentProps = {
|
||||||
|
|
||||||
export type ContentProps = {
|
export type ContentProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
tolltipContent?: ReactNode;
|
|
||||||
side?: "top" | "right" | "bottom" | "left";
|
|
||||||
};
|
};
|
||||||
export type HeaderProps = { children: ReactNode; description: string };
|
export type HeaderProps = { children: ReactNode; description: string };
|
||||||
export type TriggerProps = {
|
export type TriggerProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
tooltipContent?: ReactNode;
|
||||||
|
side?: "top" | "right" | "bottom" | "left";
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface languageMap {
|
export interface languageMap {
|
||||||
|
|
@ -301,15 +299,13 @@ export type ConfirmationModalType = {
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
title: string;
|
title: string;
|
||||||
titleHeader?: string;
|
titleHeader?: string;
|
||||||
asChild?: boolean;
|
|
||||||
destructive?: boolean;
|
destructive?: boolean;
|
||||||
modalContentTitle?: string;
|
modalContentTitle?: string;
|
||||||
cancelText: string;
|
cancelText: string;
|
||||||
confirmationText: string;
|
confirmationText: string;
|
||||||
children: [
|
children:
|
||||||
React.ReactElement<ContentProps>,
|
| [React.ReactElement<ContentProps>, React.ReactElement<TriggerProps>]
|
||||||
React.ReactElement<TriggerProps>
|
| React.ReactElement<ContentProps>;
|
||||||
];
|
|
||||||
icon: string;
|
icon: string;
|
||||||
data?: any;
|
data?: any;
|
||||||
index?: number;
|
index?: number;
|
||||||
|
|
@ -441,11 +437,6 @@ export type headerFlowsType = {
|
||||||
style?: FlowStyleType;
|
style?: FlowStyleType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type menuBarPropsType = {
|
|
||||||
flows: Array<headerFlowsType>;
|
|
||||||
tabId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type chatInputType = {
|
export type chatInputType = {
|
||||||
chatValue: string;
|
chatValue: string;
|
||||||
inputRef: {
|
inputRef: {
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,10 @@ export type AuthContextType = {
|
||||||
setIsAdmin: (isAdmin: boolean) => void;
|
setIsAdmin: (isAdmin: boolean) => void;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
accessToken: string | null;
|
accessToken: string | null;
|
||||||
refreshToken: string | null;
|
login: (accessToken: string) => void;
|
||||||
login: (accessToken: string, refreshToken: string) => void;
|
logout: () => Promise<void>;
|
||||||
logout: () => void;
|
|
||||||
userData: Users | null;
|
userData: Users | null;
|
||||||
setUserData: (userData: Users | null) => void;
|
setUserData: (userData: Users | null) => void;
|
||||||
getAuthentication: () => boolean;
|
|
||||||
authenticationErrorCount: number;
|
authenticationErrorCount: number;
|
||||||
autoLogin: boolean;
|
autoLogin: boolean;
|
||||||
setAutoLogin: (autoLogin: boolean) => void;
|
setAutoLogin: (autoLogin: boolean) => void;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
@ -1,32 +1,35 @@
|
||||||
import { XYPosition } from "reactflow";
|
import { XYPosition } from "reactflow";
|
||||||
import { tweakType } from "../components";
|
|
||||||
import { FlowType, NodeDataType } from "../flow";
|
import { FlowType, NodeDataType } from "../flow";
|
||||||
|
|
||||||
|
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||||
|
|
||||||
export type FlowsContextType = {
|
export type FlowsContextType = {
|
||||||
saveFlow: (flow: FlowType, silent?: boolean) => Promise<void>;
|
//keep
|
||||||
|
saveFlow: (flow?: FlowType, silent?: boolean) => Promise<void>;
|
||||||
tabId: string;
|
tabId: string;
|
||||||
|
//keep
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
setTabId: (index: string) => void;
|
setTabId: (index: string) => void;
|
||||||
flows: Array<FlowType>;
|
//keep
|
||||||
removeFlow: (id: string) => void;
|
removeFlow: (id: string) => void;
|
||||||
|
refreshFlows: () => void;
|
||||||
|
//keep
|
||||||
addFlow: (
|
addFlow: (
|
||||||
newProject: boolean,
|
newProject: boolean,
|
||||||
flow?: FlowType,
|
flow?: FlowType,
|
||||||
override?: boolean,
|
override?: boolean,
|
||||||
position?: XYPosition
|
position?: XYPosition
|
||||||
) => Promise<String | undefined>;
|
) => Promise<String | undefined>;
|
||||||
updateFlow: (newFlow: FlowType) => void;
|
|
||||||
incrementNodeId: () => string;
|
|
||||||
downloadFlow: (
|
downloadFlow: (
|
||||||
flow: FlowType,
|
flow: FlowType,
|
||||||
flowName: string,
|
flowName: string,
|
||||||
flowDescription?: string
|
flowDescription?: string
|
||||||
) => void;
|
) => void;
|
||||||
|
//keep
|
||||||
downloadFlows: () => void;
|
downloadFlows: () => void;
|
||||||
|
//keep
|
||||||
uploadFlows: () => void;
|
uploadFlows: () => void;
|
||||||
isBuilt: boolean;
|
setVersion: (version: string) => void;
|
||||||
setIsBuilt: (state: boolean) => void;
|
|
||||||
saveCurrentFlow: () => void;
|
|
||||||
uploadFlow: ({
|
uploadFlow: ({
|
||||||
newProject,
|
newProject,
|
||||||
file,
|
file,
|
||||||
|
|
@ -38,38 +41,28 @@ export type FlowsContextType = {
|
||||||
isComponent?: boolean;
|
isComponent?: boolean;
|
||||||
position?: XYPosition;
|
position?: XYPosition;
|
||||||
}) => Promise<String | never>;
|
}) => Promise<String | never>;
|
||||||
hardReset: () => void;
|
|
||||||
getNodeId: (nodeType: string) => string;
|
|
||||||
tabsState: FlowsState;
|
tabsState: FlowsState;
|
||||||
setTabsState: (state: FlowsState) => void;
|
setTabsState: (
|
||||||
paste: (
|
update: FlowsState | ((oldState: FlowsState) => FlowsState)
|
||||||
selection: { nodes: any; edges: any },
|
|
||||||
position: { x: number; y: number; paneX?: number; paneY?: number }
|
|
||||||
) => void;
|
) => void;
|
||||||
lastCopiedSelection: { nodes: any; edges: any } | null;
|
|
||||||
setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void;
|
|
||||||
setTweak: (tweak: tweakType) => tweakType | void;
|
|
||||||
getTweak: tweakType;
|
|
||||||
saveComponent: (
|
saveComponent: (
|
||||||
component: NodeDataType,
|
component: NodeDataType,
|
||||||
override: boolean
|
override: boolean
|
||||||
) => Promise<String | undefined>;
|
) => Promise<String | undefined>;
|
||||||
deleteComponent: (key: string) => void;
|
deleteComponent: (key: string) => void;
|
||||||
version: string;
|
version: string;
|
||||||
nodesOnFlow: string;
|
flows: Array<FlowType>;
|
||||||
setNodesOnFlow: (nodes: string) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FlowsState = {
|
export type FlowsState = {
|
||||||
[key: string]: {
|
[key: string]: FlowState | undefined;
|
||||||
isPending: boolean;
|
};
|
||||||
formKeysData: {
|
|
||||||
template?: string;
|
export type FlowState = {
|
||||||
input_keys?: Object;
|
template?: string;
|
||||||
memory_keys?: Array<string>;
|
input_keys?: Object;
|
||||||
handle_keys?: Array<string>;
|
memory_keys?: Array<string>;
|
||||||
};
|
handle_keys?: Array<string>;
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type errorsVarType = {
|
export type errorsVarType = {
|
||||||
|
|
|
||||||
|
|
@ -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[];
|
|
||||||
};
|
|
||||||
|
|
@ -1,14 +1,6 @@
|
||||||
import { Edge, Node } from "reactflow";
|
import { Edge, Node } from "reactflow";
|
||||||
import { FlowType, NodeType } from "../flow";
|
import { FlowType, NodeType } from "../flow";
|
||||||
|
|
||||||
export type cleanEdgesType = {
|
|
||||||
flow: {
|
|
||||||
edges: Edge[];
|
|
||||||
nodes: NodeType[];
|
|
||||||
};
|
|
||||||
updateEdge: (edge: Edge[]) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type unselectAllNodesType = {
|
export type unselectAllNodesType = {
|
||||||
updateNodes: (nodes: Node[]) => void;
|
updateNodes: (nodes: Node[]) => void;
|
||||||
data: Node[];
|
data: Node[];
|
||||||
|
|
|
||||||
23
src/frontend/src/types/zustand/alert/index.ts
Normal file
23
src/frontend/src/types/zustand/alert/index.ts
Normal 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;
|
||||||
|
};
|
||||||
8
src/frontend/src/types/zustand/dark/index.ts
Normal file
8
src/frontend/src/types/zustand/dark/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
export type DarkStoreType = {
|
||||||
|
dark: boolean;
|
||||||
|
stars: number;
|
||||||
|
version: string;
|
||||||
|
setDark: (dark: boolean) => void;
|
||||||
|
refreshVersion: () => void;
|
||||||
|
refreshStars: () => void;
|
||||||
|
};
|
||||||
50
src/frontend/src/types/zustand/flow/index.ts
Normal file
50
src/frontend/src/types/zustand/flow/index.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import {
|
||||||
|
Edge,
|
||||||
|
Node,
|
||||||
|
OnEdgesChange,
|
||||||
|
OnNodesChange,
|
||||||
|
ReactFlowInstance,
|
||||||
|
Viewport,
|
||||||
|
} from "reactflow";
|
||||||
|
import { FlowState } from "../../tabs";
|
||||||
|
|
||||||
|
export type FlowStoreType = {
|
||||||
|
updateSSEData: (sseData: object) => void;
|
||||||
|
sseData: object;
|
||||||
|
isBuilding: boolean;
|
||||||
|
setIsBuilding: (isBuilding: 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;
|
||||||
|
};
|
||||||
51
src/frontend/src/types/zustand/flowsManager/index.ts
Normal file
51
src/frontend/src/types/zustand/flowsManager/index.ts
Normal 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;
|
||||||
|
};
|
||||||
10
src/frontend/src/types/zustand/store/index.ts
Normal file
10
src/frontend/src/types/zustand/store/index.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
export type StoreStoreType = {
|
||||||
|
hasStore: boolean;
|
||||||
|
validApiKey: boolean;
|
||||||
|
hasApiKey: boolean;
|
||||||
|
loadingApiKey: boolean;
|
||||||
|
updateHasStore: (hasStore: boolean) => void;
|
||||||
|
updateValidApiKey: (validApiKey: boolean) => void;
|
||||||
|
updateHasApiKey: (hasApiKey: boolean) => void;
|
||||||
|
updateLoadingApiKey: (loadingApiKey: boolean) => void;
|
||||||
|
};
|
||||||
13
src/frontend/src/types/zustand/types/index.ts
Normal file
13
src/frontend/src/types/zustand/types/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { APIClassType, APIDataType } from "../../api";
|
||||||
|
|
||||||
|
export type TypesStoreType = {
|
||||||
|
types: { [char: string]: string };
|
||||||
|
setTypes: (newState: {}) => void;
|
||||||
|
templates: { [char: string]: APIClassType };
|
||||||
|
setTemplates: (newState: {}) => void;
|
||||||
|
data: APIDataType;
|
||||||
|
setData: (newState: {}) => void;
|
||||||
|
getTypes: () => Promise<void>;
|
||||||
|
setFilterEdge: (newState) => void;
|
||||||
|
getFilterEdge: any[];
|
||||||
|
};
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import _ from "lodash";
|
import _, { cloneDeep } from "lodash";
|
||||||
import {
|
import {
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
Node,
|
Node,
|
||||||
OnSelectionChangeParams,
|
OnSelectionChangeParams,
|
||||||
ReactFlowInstance,
|
|
||||||
ReactFlowJsonObject,
|
ReactFlowJsonObject,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
} from "reactflow";
|
} from "reactflow";
|
||||||
|
|
@ -13,7 +12,14 @@ import {
|
||||||
LANGFLOW_SUPPORTED_TYPES,
|
LANGFLOW_SUPPORTED_TYPES,
|
||||||
specialCharsRegex,
|
specialCharsRegex,
|
||||||
} from "../constants/constants";
|
} from "../constants/constants";
|
||||||
import { APITemplateType, TemplateVariableType } from "../types/api";
|
import { downloadFlowsFromDatabase } from "../controllers/API";
|
||||||
|
import {
|
||||||
|
APIClassType,
|
||||||
|
APIKindType,
|
||||||
|
APIObjectType,
|
||||||
|
APITemplateType,
|
||||||
|
TemplateVariableType,
|
||||||
|
} from "../types/api";
|
||||||
import {
|
import {
|
||||||
FlowType,
|
FlowType,
|
||||||
NodeDataType,
|
NodeDataType,
|
||||||
|
|
@ -22,18 +28,21 @@ import {
|
||||||
targetHandleType,
|
targetHandleType,
|
||||||
} from "../types/flow";
|
} from "../types/flow";
|
||||||
import {
|
import {
|
||||||
cleanEdgesType,
|
|
||||||
findLastNodeType,
|
findLastNodeType,
|
||||||
generateFlowType,
|
generateFlowType,
|
||||||
unselectAllNodesType,
|
unselectAllNodesType,
|
||||||
updateEdgesHandleIdsType,
|
updateEdgesHandleIdsType,
|
||||||
} from "../types/utils/reactflowUtils";
|
} from "../types/utils/reactflowUtils";
|
||||||
import { getFieldTitle, toTitleCase } from "./utils";
|
import {
|
||||||
|
createRandomKey,
|
||||||
|
getFieldTitle,
|
||||||
|
getRandomDescription,
|
||||||
|
getRandomName,
|
||||||
|
toTitleCase,
|
||||||
|
} from "./utils";
|
||||||
|
const uid = new ShortUniqueId({ length: 5 });
|
||||||
|
|
||||||
export function cleanEdges({
|
export function cleanEdges(nodes: Node[], edges: Edge[]) {
|
||||||
flow: { edges, nodes },
|
|
||||||
updateEdge,
|
|
||||||
}: cleanEdgesType) {
|
|
||||||
let newEdges = _.cloneDeep(edges);
|
let newEdges = _.cloneDeep(edges);
|
||||||
edges.forEach((edge) => {
|
edges.forEach((edge) => {
|
||||||
// check if the source and target node still exists
|
// check if the source and target node still exists
|
||||||
|
|
@ -73,7 +82,7 @@ export function cleanEdges({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
updateEdge(newEdges);
|
return newEdges;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) {
|
export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) {
|
||||||
|
|
@ -86,7 +95,8 @@ export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) {
|
||||||
|
|
||||||
export function isValidConnection(
|
export function isValidConnection(
|
||||||
{ source, target, sourceHandle, targetHandle }: Connection,
|
{ source, target, sourceHandle, targetHandle }: Connection,
|
||||||
reactFlowInstance: ReactFlowInstance
|
nodes: Node[],
|
||||||
|
edges: Edge[]
|
||||||
) {
|
) {
|
||||||
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
|
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
|
||||||
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
|
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
|
||||||
|
|
@ -100,20 +110,14 @@ export function isValidConnection(
|
||||||
t === targetHandleObject.type
|
t === targetHandleObject.type
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
let targetNode = reactFlowInstance?.getNode(target!)?.data?.node;
|
let targetNode = nodes.find((node) => node.id === target!)?.data?.node;
|
||||||
if (!targetNode) {
|
if (!targetNode) {
|
||||||
if (
|
if (!edges.find((e) => e.targetHandle === targetHandle)) {
|
||||||
!reactFlowInstance
|
|
||||||
.getEdges()
|
|
||||||
.find((e) => e.targetHandle === targetHandle)
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
(!targetNode.template[targetHandleObject.fieldName].list &&
|
(!targetNode.template[targetHandleObject.fieldName].list &&
|
||||||
!reactFlowInstance
|
!edges.find((e) => e.targetHandle === targetHandle)) ||
|
||||||
.getEdges()
|
|
||||||
.find((e) => e.targetHandle === targetHandle)) ||
|
|
||||||
targetNode.template[targetHandleObject.fieldName].list
|
targetNode.template[targetHandleObject.fieldName].list
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -157,10 +161,44 @@ export function updateTemplate(
|
||||||
return clonedObject;
|
return clonedObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateIds(
|
export const processFlows = (DbData: FlowType[], skipUpdate = true) => {
|
||||||
newFlow: ReactFlowJsonObject,
|
let savedComponents: { [key: string]: APIClassType } = {};
|
||||||
getNodeId: (type: string) => string
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { data: savedComponents, flows: DbData };
|
||||||
|
};
|
||||||
|
|
||||||
|
export 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); // Assuming updateIds is defined elsewhere
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function updateIds(newFlow: ReactFlowJsonObject) {
|
||||||
let idsMap = {};
|
let idsMap = {};
|
||||||
|
|
||||||
if (newFlow.nodes)
|
if (newFlow.nodes)
|
||||||
|
|
@ -278,6 +316,20 @@ export function validateNodes(nodes: Node[], edges: Edge[]) {
|
||||||
return nodes.flatMap((n: NodeType) => validateNode(n, edges));
|
return nodes.flatMap((n: NodeType) => validateNode(n, edges));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function 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";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function addVersionToDuplicates(flow: FlowType, flows: FlowType[]) {
|
export function addVersionToDuplicates(flow: FlowType, flows: FlowType[]) {
|
||||||
const existingNames = flows.map((item) => item.name);
|
const existingNames = flows.map((item) => item.name);
|
||||||
let newName = flow.name;
|
let newName = flow.name;
|
||||||
|
|
@ -363,6 +415,21 @@ export function handleKeyDown(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function handleOnlyIntegerInput(
|
||||||
|
event: React.KeyboardEvent<HTMLInputElement>
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
event.key === "." ||
|
||||||
|
event.key === "-" ||
|
||||||
|
event.key === "," ||
|
||||||
|
event.key === "e" ||
|
||||||
|
event.key === "E" ||
|
||||||
|
event.key === "+"
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getConnectedNodes(
|
export function getConnectedNodes(
|
||||||
edge: Edge,
|
edge: Edge,
|
||||||
nodes: Array<NodeType>
|
nodes: Array<NodeType>
|
||||||
|
|
@ -505,12 +572,28 @@ export function getMiddlePoint(nodes: Node[]) {
|
||||||
return { x: averageX, y: averageY };
|
return { x: averageX, y: averageY };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNodeId(nodeType: string) {
|
||||||
|
return nodeType + "-" + uid();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHandleId(
|
||||||
|
source: string,
|
||||||
|
sourceHandle: string,
|
||||||
|
target: string,
|
||||||
|
targetHandle: string
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
"reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function generateFlow(
|
export function generateFlow(
|
||||||
selection: OnSelectionChangeParams,
|
selection: OnSelectionChangeParams,
|
||||||
reactFlowInstance: ReactFlowInstance,
|
nodes: Node[],
|
||||||
|
edges: Edge[],
|
||||||
name: string
|
name: string
|
||||||
): generateFlowType {
|
): generateFlowType {
|
||||||
const newFlowData = reactFlowInstance.toObject();
|
const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } };
|
||||||
const uid = new ShortUniqueId({ length: 5 });
|
const uid = new ShortUniqueId({ length: 5 });
|
||||||
/* remove edges that are not connected to selected nodes on both ends
|
/* remove edges that are not connected to selected nodes on both ends
|
||||||
in future we can save this edges to when ungrouping reconect to the old nodes
|
in future we can save this edges to when ungrouping reconect to the old nodes
|
||||||
|
|
@ -543,14 +626,11 @@ export function generateFlow(
|
||||||
|
|
||||||
export function filterFlow(
|
export function filterFlow(
|
||||||
selection: OnSelectionChangeParams,
|
selection: OnSelectionChangeParams,
|
||||||
reactFlowInstance: ReactFlowInstance
|
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
||||||
|
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
|
||||||
) {
|
) {
|
||||||
reactFlowInstance.setNodes((nodes) =>
|
setNodes((nodes) => nodes.filter((node) => !selection.nodes.includes(node)));
|
||||||
nodes.filter((node) => !selection.nodes.includes(node))
|
setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge)));
|
||||||
);
|
|
||||||
reactFlowInstance.setEdges((edges) =>
|
|
||||||
edges.filter((edge) => !selection.edges.includes(edge))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findLastNode({ nodes, edges }: findLastNodeType) {
|
export function findLastNode({ nodes, edges }: findLastNodeType) {
|
||||||
|
|
@ -575,11 +655,12 @@ export function updateFlowPosition(NewPosition: XYPosition, flow: FlowType) {
|
||||||
|
|
||||||
export function concatFlows(
|
export function concatFlows(
|
||||||
flow: FlowType,
|
flow: FlowType,
|
||||||
ReactFlowInstance: ReactFlowInstance
|
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
||||||
|
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
|
||||||
) {
|
) {
|
||||||
const { nodes, edges } = flow.data!;
|
const { nodes, edges } = flow.data!;
|
||||||
ReactFlowInstance.addNodes(nodes);
|
setNodes((old) => [...old, ...nodes]);
|
||||||
ReactFlowInstance.addEdges(edges);
|
setEdges((old) => [...old, ...edges]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateSelection(
|
export function validateSelection(
|
||||||
|
|
@ -919,15 +1000,30 @@ function updateEdgesIds(edges: Edge[], idsMap: { [key: string]: string }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export 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" };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function expandGroupNode(
|
export function expandGroupNode(
|
||||||
groupNode: NodeDataType,
|
groupNode: NodeDataType,
|
||||||
ReactFlowInstance: ReactFlowInstance,
|
nodes: Node[],
|
||||||
getNodeId: (type: string) => string
|
edges: Edge[],
|
||||||
|
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
||||||
|
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
|
||||||
) {
|
) {
|
||||||
const { template, flow } = _.cloneDeep(groupNode.node!);
|
const { template, flow } = _.cloneDeep(groupNode.node!);
|
||||||
const idsMap = updateIds(flow!.data!, getNodeId);
|
const idsMap = updateIds(flow!.data!);
|
||||||
updateProxyIdsOnTemplate(template, idsMap);
|
updateProxyIdsOnTemplate(template, idsMap);
|
||||||
let flowEdges = ReactFlowInstance.getEdges();
|
let flowEdges = edges;
|
||||||
updateEdgesIds(flowEdges, idsMap);
|
updateEdgesIds(flowEdges, idsMap);
|
||||||
const gNodes: NodeType[] = flow?.data?.nodes!;
|
const gNodes: NodeType[] = flow?.data?.nodes!;
|
||||||
const gEdges = flow!.data!.edges;
|
const gEdges = flow!.data!.edges;
|
||||||
|
|
@ -1008,19 +1104,19 @@ export function expandGroupNode(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const nodes = [
|
const filteredNodes = [
|
||||||
...ReactFlowInstance.getNodes().filter((n) => n.id !== groupNode.id),
|
...nodes.filter((n) => n.id !== groupNode.id),
|
||||||
...gNodes,
|
...gNodes,
|
||||||
];
|
];
|
||||||
const edges = [
|
const filteredEdges = [
|
||||||
...ReactFlowInstance.getEdges().filter(
|
...edges.filter(
|
||||||
(e) => e.target !== groupNode.id && e.source !== groupNode.id
|
(e) => e.target !== groupNode.id && e.source !== groupNode.id
|
||||||
),
|
),
|
||||||
...gEdges,
|
...gEdges,
|
||||||
...updatedEdges,
|
...updatedEdges,
|
||||||
];
|
];
|
||||||
ReactFlowInstance.setNodes(nodes);
|
setNodes(filteredNodes);
|
||||||
ReactFlowInstance.setEdges(edges);
|
setEdges(filteredEdges);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processFlow(FlowObject: ReactFlowJsonObject) {
|
export function processFlow(FlowObject: ReactFlowJsonObject) {
|
||||||
|
|
@ -1107,3 +1203,82 @@ export function removeFileNameFromComponents(flow: FlowType) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function typesGenerator(data: APIObjectType) {
|
||||||
|
return 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;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function templatesGenerator(data: APIObjectType) {
|
||||||
|
return 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;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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 : flow.name}.json`;
|
||||||
|
|
||||||
|
// simulate a click on the link element to trigger the download
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createNewFlow = (
|
||||||
|
flowData: ReactFlowJsonObject,
|
||||||
|
flow: FlowType
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
description: flow?.description ?? getRandomDescription(),
|
||||||
|
name: flow?.name ?? getRandomName(),
|
||||||
|
data: flowData,
|
||||||
|
id: "",
|
||||||
|
is_component: flow?.is_component ?? false,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import {
|
||||||
tweakType,
|
tweakType,
|
||||||
} from "../types/components";
|
} from "../types/components";
|
||||||
import { FlowType, NodeType } from "../types/flow";
|
import { FlowType, NodeType } from "../types/flow";
|
||||||
import { FlowsState } from "../types/tabs";
|
import { FlowState, FlowsState } from "../types/tabs";
|
||||||
import { buildTweaks } from "./reactflowUtils";
|
import { buildTweaks } from "./reactflowUtils";
|
||||||
|
|
||||||
export function classNames(...classes: Array<string>): string {
|
export function classNames(...classes: Array<string>): string {
|
||||||
|
|
@ -217,13 +217,11 @@ export function groupByFamily(
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildInputs(tabsState: FlowsState, id: string): string {
|
export function buildInputs(flowState?: FlowState): string {
|
||||||
return tabsState &&
|
return flowState &&
|
||||||
tabsState[id] &&
|
flowState.input_keys &&
|
||||||
tabsState[id].formKeysData &&
|
Object.keys(flowState.input_keys!).length > 0
|
||||||
tabsState[id].formKeysData.input_keys &&
|
? JSON.stringify(flowState.input_keys)
|
||||||
Object.keys(tabsState[id].formKeysData.input_keys!).length > 0
|
|
||||||
? JSON.stringify(tabsState[id].formKeysData.input_keys)
|
|
||||||
: '{"input": "message"}';
|
: '{"input": "message"}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,18 +296,11 @@ export function buildTweakObject(tweak: tweakType) {
|
||||||
* @param {FlowsState} tabsState - The current tabs state.
|
* @param {FlowsState} tabsState - The current tabs state.
|
||||||
* @returns {string} - The chat input field
|
* @returns {string} - The chat input field
|
||||||
*/
|
*/
|
||||||
export function getChatInputField(flow: FlowType, tabsState?: FlowsState) {
|
export function getChatInputField(flow: FlowType, flowState?: FlowState) {
|
||||||
let chat_input_field = "text";
|
let chat_input_field = "text";
|
||||||
|
|
||||||
if (
|
if (flowState && flowState.input_keys) {
|
||||||
tabsState &&
|
chat_input_field = Object.keys(flowState.input_keys!)[0];
|
||||||
tabsState[flow.id] &&
|
|
||||||
tabsState[flow.id].formKeysData &&
|
|
||||||
tabsState[flow.id].formKeysData.input_keys
|
|
||||||
) {
|
|
||||||
chat_input_field = Object.keys(
|
|
||||||
tabsState[flow.id].formKeysData.input_keys!
|
|
||||||
)[0];
|
|
||||||
}
|
}
|
||||||
return chat_input_field;
|
return chat_input_field;
|
||||||
}
|
}
|
||||||
|
|
@ -323,7 +314,7 @@ export function getPythonApiCode(
|
||||||
flow: FlowType,
|
flow: FlowType,
|
||||||
isAuth: boolean,
|
isAuth: boolean,
|
||||||
tweak?: any[],
|
tweak?: any[],
|
||||||
tabsState?: FlowsState
|
flowState?: FlowState
|
||||||
): string {
|
): string {
|
||||||
const flowId = flow.id;
|
const flowId = flow.id;
|
||||||
|
|
||||||
|
|
@ -332,7 +323,7 @@ export function getPythonApiCode(
|
||||||
// node.data.id
|
// node.data.id
|
||||||
// }
|
// }
|
||||||
const tweaks = buildTweaks(flow);
|
const tweaks = buildTweaks(flow);
|
||||||
const inputs = buildInputs(tabsState!, flow.id);
|
const inputs = buildInputs(flowState);
|
||||||
return `import requests
|
return `import requests
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
@ -387,11 +378,11 @@ export function getCurlCode(
|
||||||
flow: FlowType,
|
flow: FlowType,
|
||||||
isAuth: boolean,
|
isAuth: boolean,
|
||||||
tweak?: any[],
|
tweak?: any[],
|
||||||
tabsState?: FlowsState
|
flowState?: FlowState
|
||||||
): string {
|
): string {
|
||||||
const flowId = flow.id;
|
const flowId = flow.id;
|
||||||
const tweaks = buildTweaks(flow);
|
const tweaks = buildTweaks(flow);
|
||||||
const inputs = buildInputs(tabsState!, flow.id);
|
const inputs = buildInputs(flowState);
|
||||||
|
|
||||||
return `curl -X POST \\
|
return `curl -X POST \\
|
||||||
${window.location.protocol}//${
|
${window.location.protocol}//${
|
||||||
|
|
@ -415,11 +406,11 @@ export function getCurlCode(
|
||||||
export function getPythonCode(
|
export function getPythonCode(
|
||||||
flow: FlowType,
|
flow: FlowType,
|
||||||
tweak?: any[],
|
tweak?: any[],
|
||||||
tabsState?: FlowsState
|
flowState?: FlowState
|
||||||
): string {
|
): string {
|
||||||
const flowName = flow.name;
|
const flowName = flow.name;
|
||||||
const tweaks = buildTweaks(flow);
|
const tweaks = buildTweaks(flow);
|
||||||
const inputs = buildInputs(tabsState!, flow.id);
|
const inputs = buildInputs(flowState);
|
||||||
return `from langflow import load_flow_from_json
|
return `from langflow import load_flow_from_json
|
||||||
TWEAKS = ${
|
TWEAKS = ${
|
||||||
tweak && tweak.length > 0
|
tweak && tweak.length > 0
|
||||||
|
|
@ -440,12 +431,12 @@ flow(inputs)`;
|
||||||
export function getWidgetCode(
|
export function getWidgetCode(
|
||||||
flow: FlowType,
|
flow: FlowType,
|
||||||
isAuth: boolean,
|
isAuth: boolean,
|
||||||
tabsState?: FlowsState
|
flowState?: FlowState
|
||||||
): string {
|
): string {
|
||||||
const flowId = flow.id;
|
const flowId = flow.id;
|
||||||
const flowName = flow.name;
|
const flowName = flow.name;
|
||||||
const inputs = buildInputs(tabsState!, flow.id);
|
const inputs = buildInputs(flowState);
|
||||||
let chat_input_field = getChatInputField(flow, tabsState);
|
let chat_input_field = getChatInputField(flow, flowState);
|
||||||
|
|
||||||
return `<script src="https://cdn.jsdelivr.net/gh/logspace-ai/langflow-embedded-chat@main/dist/build/static/js/bundle.min.js"></script>
|
return `<script src="https://cdn.jsdelivr.net/gh/logspace-ai/langflow-embedded-chat@main/dist/build/static/js/bundle.min.js"></script>
|
||||||
|
|
||||||
|
|
@ -456,7 +447,7 @@ chat_input_field: Input key that you want the chat to send the user message with
|
||||||
window_title="${flowName}"
|
window_title="${flowName}"
|
||||||
flow_id="${flowId}"
|
flow_id="${flowId}"
|
||||||
${
|
${
|
||||||
tabsState![flow.id] && tabsState![flow.id].formKeysData
|
flowState
|
||||||
? `chat_inputs='${inputs}'
|
? `chat_inputs='${inputs}'
|
||||||
chat_input_field="${chat_input_field}"
|
chat_input_field="${chat_input_field}"
|
||||||
`
|
`
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue