Merge branch 'cz/bug/state/zustand' into update_lc

This commit is contained in:
anovazzi1 2024-01-09 10:02:30 -03:00
commit 4591824a63
94 changed files with 2443 additions and 3090 deletions

View file

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

View file

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

View file

@ -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" },

View file

@ -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"
}

View file

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

View file

@ -1,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>
) : (
<>

View file

@ -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);
}}

View file

@ -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,
},
},
}));
}
}}
/>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,20 +1,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}

View file

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

View file

@ -1,5 +1,5 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import AccordionComponent from "../../components/AccordionComponent";
@ -28,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);
},
});
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,7 +13,7 @@ export default function InputListComponent({
editNode = false,
}: InputListComponentType): JSX.Element {
useEffect(() => {
if (disabled) {
if (disabled && value.length > 0 && value[0] !== "") {
onChange([""]);
}
}, [disabled]);
@ -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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,21 +1,24 @@
import { createContext, useContext, useEffect, useState } from "react";
import { createContext, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import Cookies from "universal-cookie";
import { autoLogin as autoLoginApi, getLoggedUser } from "../controllers/API";
import {
autoLogin as autoLoginApi,
getLoggedUser,
requestLogout,
} from "../controllers/API";
import useAlertStore from "../stores/alertStore";
import { Users } from "../types/api";
import { AuthContextType } from "../types/contexts/auth";
import { alertContext } from "./alertContext";
const initialValue: AuthContextType = {
isAdmin: false,
setIsAdmin: () => false,
isAuthenticated: false,
accessToken: null,
refreshToken: null,
login: () => {},
logout: () => {},
logout: () => new Promise(() => {}),
userData: null,
setUserData: () => {},
getAuthentication: () => false,
authenticationErrorCount: 0,
autoLogin: false,
setAutoLogin: () => {},
@ -27,24 +30,24 @@ const initialValue: AuthContextType = {
export const AuthContext = createContext<AuthContextType>(initialValue);
export function AuthProvider({ children }): React.ReactElement {
const navigate = useNavigate();
const cookies = new Cookies();
const [accessToken, setAccessToken] = useState<string | null>(
cookies.get("access_tkn_lflw")
cookies.get("access_token_lf") ?? null
);
const [refreshToken, setRefreshToken] = useState<string | null>(
cookies.get("refresh_tkn_lflw")
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(
!!cookies.get("access_token_lf")
);
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
const [isAdmin, setIsAdmin] = useState<boolean>(false);
const [userData, setUserData] = useState<Users | null>(null);
const [autoLogin, setAutoLogin] = useState<boolean>(false);
const { setLoading } = useContext(alertContext);
const setLoading = useAlertStore((state) => state.setLoading);
const [apiKey, setApiKey] = useState<string | null>(
cookies.get("apikey_tkn_lflw")
);
useEffect(() => {
const storedAccessToken = cookies.get("access_tkn_lflw");
const storedAccessToken = cookies.get("access_token_lf");
if (storedAccessToken) {
setAccessToken(storedAccessToken);
}
@ -64,7 +67,7 @@ export function AuthProvider({ children }): React.ReactElement {
.then((user) => {
if (user && user["access_token"]) {
user["refresh_token"] = "auto";
login(user["access_token"], user["refresh_token"]);
login(user["access_token"]);
setUserData(user);
setAutoLogin(true);
setLoading(false);
@ -72,48 +75,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,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
import { cloneDeep } from "lodash";
import { forwardRef, useContext, useEffect, useState } from "react";
import { useUpdateNodeInternals } from "reactflow";
import { forwardRef, useEffect, useState } from "react";
import ShadTooltip from "../../components/ShadTooltipComponent";
import CodeAreaComponent from "../../components/codeAreaComponent";
import DictComponent from "../../components/dictComponent";
@ -29,11 +28,8 @@ import {
LANGFLOW_SUPPORTED_TYPES,
limitScrollFieldsModal,
} from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { typesContext } from "../../contexts/typesContext";
import useFlowStore from "../../stores/flowStore";
import { NodeDataType } from "../../types/flow";
import { FlowsState } from "../../types/tabs";
import {
convertObjToArray,
convertValuesToNumbers,
@ -58,13 +54,11 @@ const EditNodeModal = forwardRef(
},
ref
) => {
const updateNodeInternals = useUpdateNodeInternals();
const [myData, setMyData] = useState(data);
const { setTabsState, tabId } = useContext(FlowsContext);
const { reactFlowInstance } = useContext(typesContext);
const { setModalContextOpen } = useContext(alertContext);
const setPending = useFlowStore((state) => state.setPending);
const edges = useFlowStore((state) => state.edges);
const setNode = useFlowStore((state) => state.setNode);
function changeAdvanced(n) {
setMyData((old) => {
@ -81,14 +75,12 @@ const EditNodeModal = forwardRef(
newData.node!.template[name].value = newValue;
return newData;
});
updateNodeInternals(data.id);
};
useEffect(() => {
if (open) {
setMyData(data); // reset data to what it is on node when opening modal
}
setModalContextOpen(open);
}, [open]);
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
@ -159,7 +151,7 @@ const EditNodeModal = forwardRef(
fieldName: templateParam,
};
let disabled =
reactFlowInstance?.getEdges().some(
edges.some(
(edge) =>
edge.targetHandle ===
scapedJSONStringfy(
@ -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"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { useContext, useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import SanitizedHTMLWrapper from "../../components/SanitizedHTMLWrapper";
import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
@ -13,8 +13,8 @@ import {
regexHighlight,
} from "../../constants/constants";
import { TypeModal } from "../../constants/enums";
import { alertContext } from "../../contexts/alertContext";
import { postValidatePrompt } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import { genericModalPropsType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils";
import { classNames, varHighlightHTML } from "../../utils/utils";
@ -40,9 +40,10 @@ export default function GenericModal({
const [inputValue, setInputValue] = useState(value);
const [isEdit, setIsEdit] = useState(true);
const [wordsHighlight, setWordsHighlight] = useState<string[]>([]);
const { setErrorData, setSuccessData, setNoticeData, setModalContextOpen } =
useContext(alertContext);
const ref = useRef();
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setNoticeData = useAlertStore((state) => state.setNoticeData);
const textRef = useRef<HTMLTextAreaElement>(null);
const divRef = useRef(null);
const divRefPrompt = useRef(null);
@ -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) => {

View file

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

View file

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

View file

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

View file

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

View file

@ -1,47 +1,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>
) : (
<></>

View file

@ -1,16 +1,17 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
import { Input } from "../../../../components/ui/input";
import { Separator } from "../../../../components/ui/separator";
import { alertContext } from "../../../../contexts/alertContext";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { StoreContext } from "../../../../contexts/storeContext";
import { typesContext } from "../../../../contexts/typesContext";
import ApiModal from "../../../../modals/ApiModal";
import ExportModal from "../../../../modals/exportModal";
import ShareModal from "../../../../modals/shareModal";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useStoreStore } from "../../../../stores/storeStore";
import { useTypesStore } from "../../../../stores/typesStore";
import { APIClassType, APIObjectType } from "../../../../types/api";
import {
nodeColors,
@ -26,15 +27,20 @@ import DisclosureComponent from "../DisclosureComponent";
import SidebarDraggableComponent from "./sideBarDraggableComponent";
export default function ExtraSidebar(): JSX.Element {
const { data, templates, getFilterEdge, setFilterEdge, reactFlowInstance } =
useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, version } =
useContext(FlowsContext);
const { hasApiKey, validApiKey, hasStore } = useContext(StoreContext);
const { setErrorData } = useContext(alertContext);
const data = useTypesStore((state) => state.data);
const templates = useTypesStore((state) => state.templates);
const getFilterEdge = 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">

View file

@ -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;

View file

@ -1,6 +1,5 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useState } from "react";
import { useReactFlow, useUpdateNodeInternals } from "reactflow";
import { useEffect, useState } from "react";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
import {
@ -9,12 +8,13 @@ import {
SelectItem,
SelectTrigger,
} from "../../../../components/ui/select-custom";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { StoreContext } from "../../../../contexts/storeContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import ConfirmationModal from "../../../../modals/ConfirmationModal";
import EditNodeModal from "../../../../modals/EditNodeModal";
import ShareModal from "../../../../modals/shareModal";
import { useDarkStore } from "../../../../stores/darkStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useStoreStore } from "../../../../stores/storeStore";
import { nodeToolbarPropsType } from "../../../../types/components";
import { FlowType } from "../../../../types/flow";
import {
@ -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}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,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;

View file

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

View file

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

View file

@ -0,0 +1,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 });
},
}));

View file

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

View file

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

View file

@ -3,7 +3,6 @@ import { ReactFlowJsonObject, XYPosition } from "reactflow";
import { APIClassType, APITemplateType, TemplateVariableType } from "../api";
import { ChatMessageType } from "../chat";
import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index";
import { typesContextType } from "../typesContext";
import { sourceHandleType, targetHandleType } from "./../flow/index";
export type InputComponentType = {
autoFocus?: boolean;
@ -48,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: {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,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;
};

View file

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

View 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;
};

View 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[];
};

View file

@ -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,
};
};

View file

@ -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}"
`