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