pagination and context with store
This commit is contained in:
commit
1081ace70f
21 changed files with 319 additions and 207 deletions
|
|
@ -22,49 +22,53 @@ from langflow.services.store.service import StoreService
|
||||||
router = APIRouter(prefix="/store", tags=["Components Store"])
|
router = APIRouter(prefix="/store", tags=["Components Store"])
|
||||||
|
|
||||||
|
|
||||||
def get_user_store_api_key(user: User = Depends(auth_utils.get_current_active_user)):
|
def get_user_store_api_key(
|
||||||
|
user: User = Depends(auth_utils.get_current_active_user),
|
||||||
|
settings_service=Depends(get_settings_service),
|
||||||
|
):
|
||||||
if not user.store_api_key:
|
if not user.store_api_key:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400, detail="You must have a store API key set."
|
status_code=400, detail="You must have a store API key set."
|
||||||
)
|
)
|
||||||
return user.store_api_key
|
decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service)
|
||||||
|
return decrypted
|
||||||
|
|
||||||
|
|
||||||
def get_optional_user_store_api_key(
|
def get_optional_user_store_api_key(
|
||||||
user: User = Depends(auth_utils.get_current_active_user),
|
user: User = Depends(auth_utils.get_current_active_user),
|
||||||
|
settings_service=Depends(get_settings_service),
|
||||||
):
|
):
|
||||||
return user.store_api_key
|
if not user.store_api_key:
|
||||||
|
return None
|
||||||
|
decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service)
|
||||||
|
return decrypted
|
||||||
|
|
||||||
|
|
||||||
@router.post("/components/", response_model=ComponentResponse, status_code=201)
|
@router.post("/components/", response_model=ComponentResponse, status_code=201)
|
||||||
def create_component(
|
def create_component(
|
||||||
component: StoreComponentCreate,
|
component: StoreComponentCreate,
|
||||||
store_service: StoreService = Depends(get_store_service),
|
store_service: StoreService = Depends(get_store_service),
|
||||||
settings_service=Depends(get_settings_service),
|
|
||||||
store_api_Key: str = Depends(get_user_store_api_key),
|
store_api_Key: str = Depends(get_user_store_api_key),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
decrypted = auth_utils.decrypt_api_key(store_api_Key, settings_service)
|
return store_service.upload(store_api_Key, component)
|
||||||
return store_service.upload(decrypted, component)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc))
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/components/", response_model=List[ListComponentResponse])
|
@router.get("/components/", response_model=List[ListComponentResponse])
|
||||||
def list_components(
|
def list_components(
|
||||||
|
filter_by_user: bool = Query(False),
|
||||||
page: int = 1,
|
page: int = 1,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
store_service: StoreService = Depends(get_store_service),
|
store_service: StoreService = Depends(get_store_service),
|
||||||
store_api_Key: str = Depends(get_optional_user_store_api_key),
|
store_api_Key: str = Depends(get_optional_user_store_api_key),
|
||||||
settings_service=Depends(get_settings_service),
|
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
fields = ["id", "name", "description", "user_created.name", "is_component"]
|
fields = ["id", "name", "description", "user_created.name", "is_component"]
|
||||||
if store_api_Key:
|
result = store_service.list_components(
|
||||||
decrypted = auth_utils.decrypt_api_key(store_api_Key, settings_service)
|
store_api_Key, page, limit, fields=fields, filter_by_user=filter_by_user
|
||||||
else:
|
)
|
||||||
decrypted = None
|
|
||||||
result = store_service.list_components(decrypted, page, limit, fields=fields)
|
|
||||||
return result
|
return result
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc))
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
|
@ -73,16 +77,13 @@ def list_components(
|
||||||
@router.get("/components/{component_id}", response_model=DownloadComponentResponse)
|
@router.get("/components/{component_id}", response_model=DownloadComponentResponse)
|
||||||
def read_component(
|
def read_component(
|
||||||
component_id: UUID,
|
component_id: UUID,
|
||||||
filter_by_user: bool = Query(False),
|
|
||||||
store_service: StoreService = Depends(get_store_service),
|
store_service: StoreService = Depends(get_store_service),
|
||||||
store_api_Key: str = Depends(get_user_store_api_key),
|
store_api_Key: str = Depends(get_user_store_api_key),
|
||||||
settings_service=Depends(get_settings_service),
|
|
||||||
):
|
):
|
||||||
# If the component is from the store, we need to get it from the store
|
# If the component is from the store, we need to get it from the store
|
||||||
|
|
||||||
try:
|
try:
|
||||||
decrypted = auth_utils.decrypt_api_key(store_api_Key, settings_service)
|
component = store_service.download(store_api_Key, component_id)
|
||||||
component = store_service.download(decrypted, component_id, filter_by_user)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
@ -102,6 +103,7 @@ async def search_endpoint(
|
||||||
date_from: Optional[datetime] = Query(None),
|
date_from: Optional[datetime] = Query(None),
|
||||||
date_to: Optional[datetime] = Query(None),
|
date_to: Optional[datetime] = Query(None),
|
||||||
sort: Optional[List[str]] = Query(None),
|
sort: Optional[List[str]] = Query(None),
|
||||||
|
filter_by_user: bool = Query(False),
|
||||||
fields: Optional[List[str]] = Query(None),
|
fields: Optional[List[str]] = Query(None),
|
||||||
store_service: "StoreService" = Depends(get_store_service),
|
store_service: "StoreService" = Depends(get_store_service),
|
||||||
store_api_Key: str = Depends(get_optional_user_store_api_key),
|
store_api_Key: str = Depends(get_optional_user_store_api_key),
|
||||||
|
|
@ -118,6 +120,7 @@ async def search_endpoint(
|
||||||
date_to=date_to,
|
date_to=date_to,
|
||||||
sort=sort,
|
sort=sort,
|
||||||
fields=fields,
|
fields=fields,
|
||||||
|
filter_by_user=filter_by_user,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise HTTPException(status_code=500, detail=str(exc))
|
raise HTTPException(status_code=500, detail=str(exc))
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ class StoreService(Service):
|
||||||
date_to: Optional[datetime] = None,
|
date_to: Optional[datetime] = None,
|
||||||
sort: Optional[List[str]] = ["-likes"],
|
sort: Optional[List[str]] = ["-likes"],
|
||||||
fields: Optional[List[str]] = None,
|
fields: Optional[List[str]] = None,
|
||||||
|
filter_by_user: bool = False,
|
||||||
) -> List[ComponentResponse]:
|
) -> List[ComponentResponse]:
|
||||||
# ?sort=sort,-date_created,author.name
|
# ?sort=sort,-date_created,author.name
|
||||||
|
|
||||||
|
|
@ -104,34 +105,6 @@ class StoreService(Service):
|
||||||
if fields:
|
if fields:
|
||||||
params["fields"] = ",".join(fields)
|
params["fields"] = ",".join(fields)
|
||||||
|
|
||||||
results = self._get(self.components_url, api_key, params)
|
|
||||||
return [ComponentResponse(**component) for component in results]
|
|
||||||
|
|
||||||
def list_components(
|
|
||||||
self,
|
|
||||||
api_key: str,
|
|
||||||
page: int = 1,
|
|
||||||
limit: int = 10,
|
|
||||||
fields: Optional[List[str]] = None,
|
|
||||||
) -> List[ListComponentResponse]:
|
|
||||||
params = {"page": page, "limit": limit}
|
|
||||||
# ?aggregate[count]=likes
|
|
||||||
params["fields"] = (
|
|
||||||
",".join(fields)
|
|
||||||
if fields
|
|
||||||
else ",".join(["id", "name", "description", "count(likes)", "is_component"])
|
|
||||||
)
|
|
||||||
|
|
||||||
results = self._get(self.components_url, api_key, params)
|
|
||||||
return [ListComponentResponse(**component) for component in results]
|
|
||||||
|
|
||||||
def download(
|
|
||||||
self, api_key: str, component_id: str, filter_by_user: bool
|
|
||||||
) -> DownloadComponentResponse:
|
|
||||||
url = f"{self.components_url}/{component_id}"
|
|
||||||
params = {
|
|
||||||
"fields": ",".join(["id", "name", "description", "data", "is_component"])
|
|
||||||
}
|
|
||||||
if filter_by_user:
|
if filter_by_user:
|
||||||
params["deep"] = json.dumps(
|
params["deep"] = json.dumps(
|
||||||
{
|
{
|
||||||
|
|
@ -140,6 +113,52 @@ class StoreService(Service):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
params["filter"] = json.dumps({"status": {"_eq": "public"}})
|
||||||
|
|
||||||
|
results = self._get(self.components_url, api_key, params)
|
||||||
|
return [ComponentResponse(**component) for component in results]
|
||||||
|
|
||||||
|
def list_components(
|
||||||
|
self,
|
||||||
|
api_key: str,
|
||||||
|
page: int = 1,
|
||||||
|
limit: int = 15,
|
||||||
|
fields: Optional[List[str]] = None,
|
||||||
|
filter_by_user: bool = False,
|
||||||
|
) -> List[ListComponentResponse]:
|
||||||
|
params = {"page": page, "limit": limit}
|
||||||
|
# ?aggregate[count]=likes
|
||||||
|
params["fields"] = (
|
||||||
|
",".join(fields)
|
||||||
|
if fields
|
||||||
|
else ",".join(["id", "name", "description", "count(likes)", "is_component"])
|
||||||
|
)
|
||||||
|
# Only public components or the ones created by the user
|
||||||
|
# check for "public" or "Public"
|
||||||
|
|
||||||
|
if filter_by_user:
|
||||||
|
params["deep"] = json.dumps(
|
||||||
|
{
|
||||||
|
"components": {
|
||||||
|
"_filter": {"user_created": {"token": {"_eq": api_key}}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
params["filter"] = params["filter"] = json.dumps(
|
||||||
|
{"status": {"_in": ["public", "Public"]}}
|
||||||
|
)
|
||||||
|
|
||||||
|
results = self._get(self.components_url, api_key, params)
|
||||||
|
return [ListComponentResponse(**component) for component in results]
|
||||||
|
|
||||||
|
def download(self, api_key: str, component_id: str) -> DownloadComponentResponse:
|
||||||
|
url = f"{self.components_url}/{component_id}"
|
||||||
|
params = {
|
||||||
|
"fields": ",".join(["id", "name", "description", "data", "is_component"])
|
||||||
|
}
|
||||||
|
|
||||||
component = self._get(url, api_key, params)
|
component = self._get(url, api_key, params)
|
||||||
self.call_webhook(api_key, self.webhook_url, component_id)
|
self.call_webhook(api_key, self.webhook_url, component_id)
|
||||||
|
|
||||||
|
|
|
||||||
36
src/frontend/package-lock.json
generated
36
src/frontend/package-lock.json
generated
|
|
@ -3796,9 +3796,9 @@
|
||||||
"integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g=="
|
"integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.2.31",
|
"version": "18.2.32",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.31.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.32.tgz",
|
||||||
"integrity": "sha512-c2UnPv548q+5DFh03y8lEDeMfDwBn9G3dRwfkrxQMo/dOtRHUUO57k6pHvBIfH/VF4Nh+98mZ5aaSe+2echD5g==",
|
"integrity": "sha512-F0FVIZQ1x5Gxy/VYJb7XcWvCcHR28Sjwt1dXLspdIatfPq1MVACfnBDwKe6ANLxQ64riIJooXClpUR6oxTiepg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"@types/scheduler": "*",
|
"@types/scheduler": "*",
|
||||||
|
|
@ -4517,9 +4517,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001553",
|
"version": "1.0.30001554",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001553.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001554.tgz",
|
||||||
"integrity": "sha512-N0ttd6TrFfuqKNi+pMgWJTb9qrdJu4JSpgPFLe/lrD19ugC6fZgF0pUewRowDwzdDnb9V41mFcdlYgl/PyKf4A==",
|
"integrity": "sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
|
@ -10220,9 +10220,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.3.4",
|
"version": "3.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz",
|
||||||
"integrity": "sha512-JXZNOkggUAc9T5E7nCrimoXHcSf9h3NWFe5sh36CGD/3M5TRLuQeFnQoDsit2uVTqgoOZHLx5rTykLUu16vsMQ==",
|
"integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
"arg": "^5.0.2",
|
"arg": "^5.0.2",
|
||||||
|
|
@ -13755,9 +13755,9 @@
|
||||||
"integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g=="
|
"integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g=="
|
||||||
},
|
},
|
||||||
"@types/react": {
|
"@types/react": {
|
||||||
"version": "18.2.31",
|
"version": "18.2.32",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.31.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.32.tgz",
|
||||||
"integrity": "sha512-c2UnPv548q+5DFh03y8lEDeMfDwBn9G3dRwfkrxQMo/dOtRHUUO57k6pHvBIfH/VF4Nh+98mZ5aaSe+2echD5g==",
|
"integrity": "sha512-F0FVIZQ1x5Gxy/VYJb7XcWvCcHR28Sjwt1dXLspdIatfPq1MVACfnBDwKe6ANLxQ64riIJooXClpUR6oxTiepg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"@types/scheduler": "*",
|
"@types/scheduler": "*",
|
||||||
|
|
@ -14248,9 +14248,9 @@
|
||||||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
|
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
|
||||||
},
|
},
|
||||||
"caniuse-lite": {
|
"caniuse-lite": {
|
||||||
"version": "1.0.30001553",
|
"version": "1.0.30001554",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001553.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001554.tgz",
|
||||||
"integrity": "sha512-N0ttd6TrFfuqKNi+pMgWJTb9qrdJu4JSpgPFLe/lrD19ugC6fZgF0pUewRowDwzdDnb9V41mFcdlYgl/PyKf4A=="
|
"integrity": "sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ=="
|
||||||
},
|
},
|
||||||
"ccount": {
|
"ccount": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
|
|
@ -18070,9 +18070,9 @@
|
||||||
"integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ=="
|
"integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ=="
|
||||||
},
|
},
|
||||||
"tailwindcss": {
|
"tailwindcss": {
|
||||||
"version": "3.3.4",
|
"version": "3.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz",
|
||||||
"integrity": "sha512-JXZNOkggUAc9T5E7nCrimoXHcSf9h3NWFe5sh36CGD/3M5TRLuQeFnQoDsit2uVTqgoOZHLx5rTykLUu16vsMQ==",
|
"integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
"arg": "^5.0.2",
|
"arg": "^5.0.2",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ export default function PaginatorComponent({
|
||||||
rowsCount = [10, 20, 50, 100],
|
rowsCount = [10, 20, 50, 100],
|
||||||
totalRowsCount = 0,
|
totalRowsCount = 0,
|
||||||
paginate,
|
paginate,
|
||||||
|
storeComponent = false,
|
||||||
}: PaginatorComponentType) {
|
}: PaginatorComponentType) {
|
||||||
const [size, setPageSize] = useState(pageSize);
|
const [size, setPageSize] = useState(pageSize);
|
||||||
const [maxIndex, setMaxPageIndex] = useState(
|
const [maxIndex, setMaxPageIndex] = useState(
|
||||||
|
|
@ -30,7 +31,13 @@ export default function PaginatorComponent({
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between px-2">
|
<div className="flex items-center justify-between px-2">
|
||||||
<div className="flex-1 text-sm text-muted-foreground"></div>
|
<div className="flex-1 text-sm text-muted-foreground"></div>
|
||||||
<div className="flex items-center space-x-6 lg:space-x-8">
|
<div
|
||||||
|
className={
|
||||||
|
storeComponent
|
||||||
|
? "flex items-center lg:space-x-8 "
|
||||||
|
: "flex items-center space-x-6 lg:space-x-8 "
|
||||||
|
}
|
||||||
|
>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<p className="text-sm font-medium">Rows per page</p>
|
<p className="text-sm font-medium">Rows per page</p>
|
||||||
<Select
|
<Select
|
||||||
|
|
@ -54,7 +61,8 @@ export default function PaginatorComponent({
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||||
Page {pageIndex} of {maxIndex}
|
Page {pageIndex}
|
||||||
|
{!storeComponent && <> of {maxIndex}</>}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export default function Header(): JSX.Element {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="header-arrangement">
|
<div className="header-arrangement">
|
||||||
<div className="header-start-display">
|
<div className="header-start-display lg:w-[30%]">
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
<span className="ml-4 text-2xl">⛓️</span>
|
<span className="ml-4 text-2xl">⛓️</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -47,14 +47,19 @@ export default function Header(): JSX.Element {
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
<Button
|
<Button
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
variant={location.pathname === "/" ? "primary" : "secondary"}
|
variant={
|
||||||
|
location.pathname === "/flows" ||
|
||||||
|
location.pathname === "/components"
|
||||||
|
? "primary"
|
||||||
|
: "secondary"
|
||||||
|
}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
<IconComponent name="Home" className="h-4 w-4" />
|
<IconComponent name="Home" className="h-4 w-4" />
|
||||||
<div className="flex-1">{USER_PROJECTS_HEADER}</div>
|
<div className="hidden flex-1 md:block">{USER_PROJECTS_HEADER}</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/community">
|
{/* <Link to="/community">
|
||||||
<Button
|
<Button
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
variant={
|
variant={
|
||||||
|
|
@ -65,7 +70,7 @@ export default function Header(): JSX.Element {
|
||||||
<IconComponent name="Users2" className="h-4 w-4" />
|
<IconComponent name="Users2" className="h-4 w-4" />
|
||||||
<div className="flex-1">Community Examples</div>
|
<div className="flex-1">Community Examples</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link> */}
|
||||||
{hasStore && (
|
{hasStore && (
|
||||||
<Link to="/store">
|
<Link to="/store">
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -79,16 +84,16 @@ export default function Header(): JSX.Element {
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="header-end-division">
|
<div className="header-end-division lg:w-[30%]">
|
||||||
<div className="header-end-display">
|
<div className="header-end-display">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/logspace-ai/langflow"
|
href="https://github.com/logspace-ai/langflow"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="header-github-link"
|
className="header-github-link gap-2"
|
||||||
>
|
>
|
||||||
<FaGithub className="mr-2 h-5 w-5" />
|
<FaGithub className="h-5 w-5" />
|
||||||
Star
|
<div className="hidden lg:block">Star</div>
|
||||||
<div className="header-github-display">{stars}</div>
|
<div className="header-github-display">{stars}</div>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import { createContext, useState } from "react";
|
import { createContext, useState } from "react";
|
||||||
import { checkHasStore } from "../controllers/API";
|
import { checkHasStore } from "../controllers/API";
|
||||||
import { storeContextType } from "../types/contexts/store";
|
import { storeContextType } from "../types/contexts/store";
|
||||||
import { FlowType } from "../types/flow";
|
|
||||||
|
|
||||||
//store context to share user components and update them
|
//store context to share user components and update them
|
||||||
const initialValue = {
|
const initialValue = {
|
||||||
savedFlows: {},
|
savedFlows: new Set<string>(),
|
||||||
setSavedFlows: () => {},
|
setSavedFlows: () => {},
|
||||||
hasStore: false,
|
hasStore: false,
|
||||||
setHasStore: () => {},
|
setHasStore: () => {},
|
||||||
|
|
@ -14,7 +13,8 @@ const initialValue = {
|
||||||
export const StoreContext = createContext<storeContextType>(initialValue);
|
export const StoreContext = createContext<storeContextType>(initialValue);
|
||||||
|
|
||||||
export function StoreProvider({ children }) {
|
export function StoreProvider({ children }) {
|
||||||
const [savedFlows, setSavedFlows] = useState<{ [key: string]: FlowType }>({});
|
const [savedFlows, setSavedFlows] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const [hasStore, setHasStore] = useState(false);
|
const [hasStore, setHasStore] = useState(false);
|
||||||
|
|
||||||
checkHasStore().then((res) => {
|
checkHasStore().then((res) => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import _ from "lodash";
|
import _, { cloneDeep } from "lodash";
|
||||||
import {
|
import {
|
||||||
ReactNode,
|
ReactNode,
|
||||||
createContext,
|
createContext,
|
||||||
|
|
@ -40,7 +40,7 @@ import {
|
||||||
updateTemplate,
|
updateTemplate,
|
||||||
} from "../utils/reactflowUtils";
|
} from "../utils/reactflowUtils";
|
||||||
import {
|
import {
|
||||||
IncrementObjectKey,
|
createRandomKey,
|
||||||
getRandomDescription,
|
getRandomDescription,
|
||||||
getRandomName,
|
getRandomName,
|
||||||
getSetFromObject,
|
getSetFromObject,
|
||||||
|
|
@ -80,7 +80,7 @@ const TabsContextInitialValue: TabsContextType = {
|
||||||
selection: { nodes: any; edges: any },
|
selection: { nodes: any; edges: any },
|
||||||
position: { x: number; y: number; paneX?: number; paneY?: number }
|
position: { x: number; y: number; paneX?: number; paneY?: number }
|
||||||
) => {},
|
) => {},
|
||||||
saveComponent: (component: NodeDataType) => {},
|
saveComponent: async (component: NodeDataType) => "",
|
||||||
deleteComponent: (id: string, key: string) => {},
|
deleteComponent: (id: string, key: string) => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -154,8 +154,14 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (flow.data && flow.is_component) {
|
if (flow.data && flow.is_component) {
|
||||||
storeComponents[(flow.data.nodes[0].data as NodeDataType).type] =
|
(flow.data.nodes[0].data as NodeDataType).node!.display_name =
|
||||||
_.cloneDeep((flow.data.nodes[0].data as NodeDataType).node!);
|
flow.name;
|
||||||
|
storeComponents[
|
||||||
|
createRandomKey(
|
||||||
|
(flow.data.nodes[0].data as NodeDataType).type,
|
||||||
|
uid()
|
||||||
|
)
|
||||||
|
] = _.cloneDeep((flow.data.nodes[0].data as NodeDataType).node!);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!skipUpdate) processDataFromFlow(flow, false);
|
if (!skipUpdate) processDataFromFlow(flow, false);
|
||||||
|
|
@ -165,9 +171,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
});
|
});
|
||||||
setData((prev) => {
|
setData((prev) => {
|
||||||
let newData = _.cloneDeep(prev);
|
let newData = _.cloneDeep(prev);
|
||||||
Object.keys(storeComponents).forEach((key) => {
|
|
||||||
newData["custom_components"][key] = storeComponents[key];
|
const customComponent = newData["custom_components"]["CustomComponent"];
|
||||||
});
|
newData["custom_components"] = cloneDeep(storeComponents);
|
||||||
|
newData["custom_components"]["CustomComponent"] = customComponent;
|
||||||
return newData;
|
return newData;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -458,6 +465,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
id: source,
|
id: source,
|
||||||
});
|
});
|
||||||
sourceHandleObject.id = source;
|
sourceHandleObject.id = source;
|
||||||
|
|
||||||
edge.data.sourceHandle = sourceHandleObject;
|
edge.data.sourceHandle = sourceHandleObject;
|
||||||
const targetHandleObject: targetHandleType = scapeJSONParse(
|
const targetHandleObject: targetHandleType = scapeJSONParse(
|
||||||
edge.targetHandle!
|
edge.targetHandle!
|
||||||
|
|
@ -482,6 +490,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
sourceHandle,
|
sourceHandle,
|
||||||
targetHandle,
|
targetHandle,
|
||||||
id,
|
id,
|
||||||
|
data: cloneDeep(edge.data),
|
||||||
style: { stroke: "#555" },
|
style: { stroke: "#555" },
|
||||||
className:
|
className:
|
||||||
targetHandleObject.type === "Text"
|
targetHandleObject.type === "Text"
|
||||||
|
|
@ -538,7 +547,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
let data = flow?.data ? flow.data : null;
|
let data = flow?.data ? flow.data : null;
|
||||||
if (data) {
|
if (data) {
|
||||||
processFlowEdges(flow);
|
processFlowEdges(flow);
|
||||||
processFlowNodes(flow);
|
//prevent node update for now
|
||||||
|
// processFlowNodes(flow);
|
||||||
//add animation to text type edges
|
//add animation to text type edges
|
||||||
updateEdges(data.edges);
|
updateEdges(data.edges);
|
||||||
// updateNodes(data.nodes, data.edges);
|
// updateNodes(data.nodes, data.edges);
|
||||||
|
|
@ -605,7 +615,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
const addFlowToLocalState = (newFlow: FlowType) => {
|
const addFlowToLocalState = (newFlow: FlowType) => {
|
||||||
let newFlows: FlowType[] = [];
|
let newFlows: FlowType[] = [];
|
||||||
setFlows((prevState) => {
|
setFlows((prevState) => {
|
||||||
newFlows.concat(prevState);
|
newFlows = newFlows.concat(prevState);
|
||||||
newFlows.push(newFlow);
|
newFlows.push(newFlow);
|
||||||
return [...prevState, newFlow];
|
return [...prevState, newFlow];
|
||||||
});
|
});
|
||||||
|
|
@ -671,12 +681,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
component.node!.official = false;
|
component.node!.official = false;
|
||||||
let key = component.type;
|
let key = component.type;
|
||||||
if (data["custom_components"][key] !== undefined) {
|
if (data["custom_components"][key] !== undefined) {
|
||||||
let { newKey, increment } = IncrementObjectKey(
|
let increment: number;
|
||||||
data["custom_components"],
|
component.type = createRandomKey(key, uid());
|
||||||
key
|
|
||||||
);
|
|
||||||
key = newKey;
|
|
||||||
component.type = newKey;
|
|
||||||
let componentNodes: { [key: string]: APIClassType } = {};
|
let componentNodes: { [key: string]: APIClassType } = {};
|
||||||
Object.keys(data["custom_components"]).forEach((key) => {
|
Object.keys(data["custom_components"]).forEach((key) => {
|
||||||
componentNodes[key] = data["custom_components"][key];
|
componentNodes[key] = data["custom_components"][key];
|
||||||
|
|
@ -697,7 +703,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
` (${increment})`;
|
` (${increment})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addFlow(true, createFlowComponent(component));
|
return addFlow(true, createFlowComponent(component));
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteComponent(id: string, key: string) {
|
function deleteComponent(id: string, key: string) {
|
||||||
|
|
|
||||||
|
|
@ -597,6 +597,20 @@ export async function getStoreComponents(page: number = 1, limit: number = 10) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getStoreSavedComponents() {
|
||||||
|
try {
|
||||||
|
const res = await api.get(
|
||||||
|
`${BASE_URL_API}store/components/?filter_by_user=true`
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function postStoreComponents(component: Component) {
|
export async function postStoreComponents(component: Component) {
|
||||||
try {
|
try {
|
||||||
const res = await api.post(`${BASE_URL_API}store/components/`, component);
|
const res = await api.post(`${BASE_URL_API}store/components/`, component);
|
||||||
|
|
@ -676,3 +690,15 @@ export async function checkHasStore() {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getNumberOfComponents() {
|
||||||
|
try {
|
||||||
|
const res = await api.get(`${BASE_URL_API}store/components/count`);
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
import { useUpdateNodeInternals } from "reactflow";
|
||||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||||
import CodeAreaComponent from "../../components/codeAreaComponent";
|
import CodeAreaComponent from "../../components/codeAreaComponent";
|
||||||
import DictComponent from "../../components/dictComponent";
|
import DictComponent from "../../components/dictComponent";
|
||||||
|
|
@ -64,6 +65,7 @@ const EditNodeModal = forwardRef(
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const [modalOpen, setModalOpen] = useState(open ?? false);
|
const [modalOpen, setModalOpen] = useState(open ?? false);
|
||||||
|
const updateNodeInternals = useUpdateNodeInternals();
|
||||||
|
|
||||||
const myData = useRef(data);
|
const myData = useRef(data);
|
||||||
|
|
||||||
|
|
@ -83,11 +85,14 @@ const EditNodeModal = forwardRef(
|
||||||
const handleOnNewValue = (newValue: any, name) => {
|
const handleOnNewValue = (newValue: any, name) => {
|
||||||
myData.current.node!.template[name].value = newValue;
|
myData.current.node!.template[name].value = newValue;
|
||||||
setDataValue(newValue);
|
setDataValue(newValue);
|
||||||
|
updateNodeInternals(data.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (modalOpen) {
|
||||||
myData.current = data; // reset data to what it is on node when opening modal
|
myData.current = data; // reset data to what it is on node when opening modal
|
||||||
onClose!(modalOpen);
|
onClose!(modalOpen);
|
||||||
|
}
|
||||||
}, [modalOpen]);
|
}, [modalOpen]);
|
||||||
|
|
||||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -310,7 +310,6 @@ export default function Page({
|
||||||
event.dataTransfer.getData("nodedata")
|
event.dataTransfer.getData("nodedata")
|
||||||
);
|
);
|
||||||
|
|
||||||
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
|
|
||||||
// Calculate the position where the node should be created
|
// Calculate the position where the node should be created
|
||||||
const position = reactFlowInstance!.project({
|
const position = reactFlowInstance!.project({
|
||||||
x: event.clientX - reactflowBounds!.left,
|
x: event.clientX - reactflowBounds!.left,
|
||||||
|
|
|
||||||
|
|
@ -309,7 +309,12 @@ export default function ExtraSidebar(): JSX.Element {
|
||||||
>
|
>
|
||||||
<div className="side-bar-components-gap">
|
<div className="side-bar-components-gap">
|
||||||
{Object.keys(dataFilter[SBSectionName])
|
{Object.keys(dataFilter[SBSectionName])
|
||||||
.sort(sensitiveSort)
|
.sort((a, b) =>
|
||||||
|
sensitiveSort(
|
||||||
|
dataFilter[SBSectionName][a].display_name,
|
||||||
|
dataFilter[SBSectionName][b].display_name
|
||||||
|
)
|
||||||
|
)
|
||||||
.map((SBItemName: string, index) => (
|
.map((SBItemName: string, index) => (
|
||||||
<ShadTooltip
|
<ShadTooltip
|
||||||
content={
|
content={
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,9 @@ export default function NodeToolbarComponent({
|
||||||
|
|
||||||
function handleShareComponent() {
|
function handleShareComponent() {
|
||||||
const componentFlow = cloneDeep(data);
|
const componentFlow = cloneDeep(data);
|
||||||
|
saveComponent(componentFlow).then(() => {
|
||||||
saveFlowStore(createFlowComponent(componentFlow)).then(
|
saveFlowStore(createFlowComponent(componentFlow)).then(
|
||||||
() => {
|
(_) => {
|
||||||
saveComponent(componentFlow);
|
|
||||||
setSuccessData({
|
setSuccessData({
|
||||||
title: "Component shared successfully",
|
title: "Component shared successfully",
|
||||||
});
|
});
|
||||||
|
|
@ -84,6 +84,7 @@ export default function NodeToolbarComponent({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const handleSelectChange = (event) => {
|
const handleSelectChange = (event) => {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
|
|
@ -98,7 +99,6 @@ export default function NodeToolbarComponent({
|
||||||
downloadNode(createFlowComponent(cloneDeep(data)));
|
downloadNode(createFlowComponent(cloneDeep(data)));
|
||||||
break;
|
break;
|
||||||
case "Share":
|
case "Share":
|
||||||
console.log("Share");
|
|
||||||
setShowconfirmShare(true);
|
setShowconfirmShare(true);
|
||||||
break;
|
break;
|
||||||
case "SaveAll":
|
case "SaveAll":
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,6 @@ export default function ComponentsComponent() {
|
||||||
<SkeletonCardComponent />
|
<SkeletonCardComponent />
|
||||||
<SkeletonCardComponent />
|
<SkeletonCardComponent />
|
||||||
<SkeletonCardComponent />
|
<SkeletonCardComponent />
|
||||||
<SkeletonCardComponent />
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
flows
|
flows
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,6 @@ export default function FlowsComponent() {
|
||||||
<SkeletonCardComponent />
|
<SkeletonCardComponent />
|
||||||
<SkeletonCardComponent />
|
<SkeletonCardComponent />
|
||||||
<SkeletonCardComponent />
|
<SkeletonCardComponent />
|
||||||
<SkeletonCardComponent />
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
flows
|
flows
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Link, ToyBrick } from "lucide-react";
|
import { Link, ToyBrick } from "lucide-react";
|
||||||
import { useContext, useRef, useState } from "react";
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
import IconComponent from "../../../components/genericIconComponent";
|
import IconComponent from "../../../components/genericIconComponent";
|
||||||
import { Badge } from "../../../components/ui/badge";
|
import { Badge } from "../../../components/ui/badge";
|
||||||
import { Button } from "../../../components/ui/button";
|
import { Button } from "../../../components/ui/button";
|
||||||
|
|
@ -10,6 +10,8 @@ import {
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../../../components/ui/card";
|
} from "../../../components/ui/card";
|
||||||
|
import { alertContext } from "../../../contexts/alertContext";
|
||||||
|
import { StoreContext } from "../../../contexts/storeContext";
|
||||||
import { TabsContext } from "../../../contexts/tabsContext";
|
import { TabsContext } from "../../../contexts/tabsContext";
|
||||||
import { getComponent, saveFlowStore } from "../../../controllers/API";
|
import { getComponent, saveFlowStore } from "../../../controllers/API";
|
||||||
import { FlowType } from "../../../types/flow";
|
import { FlowType } from "../../../types/flow";
|
||||||
|
|
@ -17,12 +19,19 @@ import { FlowComponent } from "../../../types/store";
|
||||||
import cloneFLowWithParent from "../../../utils/storeUtils";
|
import cloneFLowWithParent from "../../../utils/storeUtils";
|
||||||
|
|
||||||
export const MarketCardComponent = ({ data }: { data: FlowComponent }) => {
|
export const MarketCardComponent = ({ data }: { data: FlowComponent }) => {
|
||||||
const [added, setAdded] = useState(false);
|
const { savedFlows } = useContext(StoreContext);
|
||||||
|
const [added, setAdded] = useState(savedFlows.has(data.id) ? true : false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { addFlow } = useContext(TabsContext);
|
const { addFlow } = useContext(TabsContext);
|
||||||
|
const { setSuccessData } = useContext(alertContext);
|
||||||
const flowData = useRef<FlowType>();
|
const flowData = useRef<FlowType>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAdded(savedFlows.has(data.id) ? true : false);
|
||||||
|
}, [savedFlows]);
|
||||||
|
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
|
setLoading(true);
|
||||||
getComponent(data.id).then(
|
getComponent(data.id).then(
|
||||||
(res) => {
|
(res) => {
|
||||||
console.log(res);
|
console.log(res);
|
||||||
|
|
@ -45,7 +54,19 @@ export const MarketCardComponent = ({ data }: { data: FlowComponent }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInstall() {
|
function handleInstall() {
|
||||||
addFlow(true, flowData.current!);
|
if (flowData.current) {
|
||||||
|
addFlow(true, flowData.current!).then(() => {
|
||||||
|
setSuccessData({ title: "Flow Installed" });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getComponent(data.id).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
const newFLow = cloneFLowWithParent(res, res.id, data.is_component);
|
||||||
|
flowData.current = newFLow;
|
||||||
|
addFlow(true, newFLow);
|
||||||
|
setSuccessData({ title: "Flow Installed" });
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFork(flowId: string, is_component: boolean) {
|
function handleFork(flowId: string, is_component: boolean) {
|
||||||
|
|
@ -145,7 +166,6 @@ export const MarketCardComponent = ({ data }: { data: FlowComponent }) => {
|
||||||
size="sm"
|
size="sm"
|
||||||
className="whitespace-nowrap "
|
className="whitespace-nowrap "
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLoading(true);
|
|
||||||
if (!added) {
|
if (!added) {
|
||||||
handleAdd();
|
handleAdd();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { useContext, useEffect, useState } from "react";
|
||||||
import PaginatorComponent from "../../components/PaginatorComponent";
|
import PaginatorComponent from "../../components/PaginatorComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import Header from "../../components/headerComponent";
|
import Header from "../../components/headerComponent";
|
||||||
|
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
|
||||||
import { Badge } from "../../components/ui/badge";
|
import { Badge } from "../../components/ui/badge";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Input } from "../../components/ui/input";
|
import { Input } from "../../components/ui/input";
|
||||||
|
|
@ -16,53 +17,61 @@ import {
|
||||||
} from "../../components/ui/select";
|
} from "../../components/ui/select";
|
||||||
import { Switch } from "../../components/ui/switch";
|
import { Switch } from "../../components/ui/switch";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { StoreContext } from "../../contexts/storeContext";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
import { getStoreComponents, searchComponent } from "../../controllers/API";
|
import {
|
||||||
|
getStoreComponents,
|
||||||
|
getStoreSavedComponents,
|
||||||
|
searchComponent,
|
||||||
|
} from "../../controllers/API";
|
||||||
import StoreApiKeyModal from "../../modals/StoreApiKeyModal";
|
import StoreApiKeyModal from "../../modals/StoreApiKeyModal";
|
||||||
import { FlowComponent } from "../../types/store";
|
import { FlowComponent } from "../../types/store";
|
||||||
import { cn } from "../../utils/utils";
|
import { cn } from "../../utils/utils";
|
||||||
import { MarketCardComponent } from "./components/market-card";
|
import { MarketCardComponent } from "./components/market-card";
|
||||||
export default function StorePage(): JSX.Element {
|
export default function StorePage(): JSX.Element {
|
||||||
const { setTabId } = useContext(TabsContext);
|
const { setTabId } = useContext(TabsContext);
|
||||||
|
|
||||||
const { setApiKey, apiKey } = useContext(AuthContext);
|
|
||||||
|
|
||||||
// set null id
|
// set null id
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTabId("");
|
setTabId("");
|
||||||
}, []);
|
}, []);
|
||||||
const [data, setData] = useState<FlowComponent[]>([]);
|
const [data, setData] = useState<FlowComponent[]>([]);
|
||||||
const [dataSelect, setDataSelect] = useState<FlowComponent[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [search, setSearch] = useState(false);
|
||||||
const [filteredCategories, setFilteredCategories] = useState(new Set());
|
const [filteredCategories, setFilteredCategories] = useState(new Set());
|
||||||
const [inputText, setInputText] = useState<string>("");
|
const [inputText, setInputText] = useState<string>("");
|
||||||
const [searchData, setSearchData] = useState(data);
|
const [searchData, setSearchData] = useState(data);
|
||||||
const [errorApiKey, setErrorApiKey] = useState(false);
|
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
const { addFlow } = useContext(TabsContext);
|
|
||||||
const [totalRowsCount, setTotalRowsCount] = useState(0);
|
const [totalRowsCount, setTotalRowsCount] = useState(0);
|
||||||
const [size, setPageSize] = useState(10);
|
const [size, setPageSize] = useState(10);
|
||||||
const [index, setPageIndex] = useState(1);
|
const [index, setPageIndex] = useState(1);
|
||||||
|
const { setSavedFlows } = useContext(StoreContext);
|
||||||
|
|
||||||
|
async function getSavedComponents() {
|
||||||
|
setLoading(true);
|
||||||
|
const result = await getStoreSavedComponents();
|
||||||
|
let savedIds = new Set<string>();
|
||||||
|
result.forEach((flow) => {
|
||||||
|
savedIds.add(flow.id);
|
||||||
|
});
|
||||||
|
setSavedFlows(savedIds);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleGetComponents();
|
getSavedComponents().then((_) => handleGetComponents());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleGetComponents = () => {
|
const handleGetComponents = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
getStoreComponents(index - 1, 10000)
|
getStoreComponents(index - 1, 10000)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setSearchData(res.slice(0, 10));
|
setSearchData(res.slice(0, size));
|
||||||
setLoading(false);
|
|
||||||
setErrorApiKey(false);
|
|
||||||
setData(res);
|
|
||||||
setTotalRowsCount(res.length);
|
setTotalRowsCount(res.length);
|
||||||
|
setData(res);
|
||||||
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setSearchData([]);
|
setSearchData([]);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setErrorApiKey(true);
|
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "Error to get components.",
|
title: "Error to get components.",
|
||||||
list: [err["response"]["data"]["detail"]],
|
list: [err["response"]["data"]["detail"]],
|
||||||
|
|
@ -71,6 +80,12 @@ export default function StorePage(): JSX.Element {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = (inputText: string) => {
|
const handleSearch = (inputText: string) => {
|
||||||
|
if (inputText === "") {
|
||||||
|
handleGetComponents();
|
||||||
|
setSearch(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSearch(true);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
searchComponent(inputText).then(
|
searchComponent(inputText).then(
|
||||||
(res) => {
|
(res) => {
|
||||||
|
|
@ -87,17 +102,15 @@ export default function StorePage(): JSX.Element {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
getStoreComponents(pageIndex, pageSize)
|
getStoreComponents(pageIndex, pageSize)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
setSearchData(res.slice(0, size));
|
||||||
|
setData(res);
|
||||||
setPageIndex(pageIndex);
|
setPageIndex(pageIndex);
|
||||||
setPageSize(pageSize);
|
setPageSize(pageSize);
|
||||||
setSearchData(res);
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setErrorApiKey(false);
|
|
||||||
setData(res);
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setSearchData([]);
|
setSearchData([]);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setErrorApiKey(true);
|
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "Error to get components.",
|
title: "Error to get components.",
|
||||||
list: [err["response"]["data"]["detail"]],
|
list: [err["response"]["data"]["detail"]],
|
||||||
|
|
@ -105,14 +118,9 @@ export default function StorePage(): JSX.Element {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetFilter() {
|
|
||||||
setPageIndex(1);
|
|
||||||
setPageSize(10);
|
|
||||||
handleGetComponents();
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadingWithApiKey = loading;
|
const loadingWithApiKey = loading;
|
||||||
const renderComponents = !loading;
|
const renderComponents = !loading;
|
||||||
|
const renderPagination = searchData.length > 0 && !loading && !search;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -189,8 +197,9 @@ export default function StorePage(): JSX.Element {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center gap-4">
|
<div className="flex h-2 items-center justify-center gap-4">
|
||||||
{Array.from(new Set(searchData.map((i) => i.is_component))).map(
|
{renderComponents &&
|
||||||
|
Array.from(new Set(searchData.map((i) => i.is_component))).map(
|
||||||
(i, idx) => (
|
(i, idx) => (
|
||||||
<Badge
|
<Badge
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -221,11 +230,15 @@ export default function StorePage(): JSX.Element {
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{renderComponents && (
|
|
||||||
<>
|
|
||||||
<div className="mt-6 grid w-full gap-4 md:grid-cols-2 lg:grid-cols-3">
|
<div className="mt-6 grid w-full gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{searchData
|
{loadingWithApiKey ? (
|
||||||
|
<>
|
||||||
|
<SkeletonCardComponent />
|
||||||
|
<SkeletonCardComponent />
|
||||||
|
<SkeletonCardComponent />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
searchData
|
||||||
.filter(
|
.filter(
|
||||||
(f) =>
|
(f) =>
|
||||||
Array.from(filteredCategories).length === 0 ||
|
Array.from(filteredCategories).length === 0 ||
|
||||||
|
|
@ -233,12 +246,12 @@ export default function StorePage(): JSX.Element {
|
||||||
)
|
)
|
||||||
.map((item, idx) => (
|
.map((item, idx) => (
|
||||||
<MarketCardComponent key={idx} data={item} />
|
<MarketCardComponent key={idx} data={item} />
|
||||||
))}
|
))
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{totalRowsCount > 0 && !loading && (
|
</div>
|
||||||
|
|
||||||
|
{renderPagination && (
|
||||||
<div className="relative bottom-2">
|
<div className="relative bottom-2">
|
||||||
<PaginatorComponent
|
<PaginatorComponent
|
||||||
storeComponent={true}
|
storeComponent={true}
|
||||||
|
|
@ -251,9 +264,6 @@ export default function StorePage(): JSX.Element {
|
||||||
></PaginatorComponent>
|
></PaginatorComponent>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{loadingWithApiKey && (
|
|
||||||
<div className="flex w-full flex-col gap-4 p-4">Loading...</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@
|
||||||
@apply flex gap-2;
|
@apply flex gap-2;
|
||||||
}
|
}
|
||||||
.primary-input {
|
.primary-input {
|
||||||
@apply form-input block w-full truncate rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm;
|
@apply form-input block w-full truncate rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 text-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-card {
|
.skeleton-card {
|
||||||
|
|
@ -237,7 +237,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-page-flows-display {
|
.main-page-flows-display {
|
||||||
@apply grid w-full gap-4 md:grid-cols-2 lg:grid-cols-4;
|
@apply grid w-full gap-4 md:grid-cols-2 lg:grid-cols-3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.community-page-arrangement {
|
.community-page-arrangement {
|
||||||
|
|
@ -495,7 +495,7 @@
|
||||||
@apply flex items-center gap-0.5 rounded-md px-1.5 py-1 text-sm font-medium;
|
@apply flex items-center gap-0.5 rounded-md px-1.5 py-1 text-sm font-medium;
|
||||||
}
|
}
|
||||||
.header-menu-bar-display {
|
.header-menu-bar-display {
|
||||||
@apply flex max-w-[200px] cursor-pointer items-center gap-2;
|
@apply flex max-w-[120px] lg:max-w-[200px] cursor-pointer items-center gap-2;
|
||||||
}
|
}
|
||||||
.header-menu-flow-name {
|
.header-menu-flow-name {
|
||||||
@apply flex-1 truncate;
|
@apply flex-1 truncate;
|
||||||
|
|
@ -505,13 +505,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-arrangement {
|
.header-arrangement {
|
||||||
@apply flex-max-width h-12 items-center justify-between border-border bg-background;
|
@apply flex-max-width h-12 items-center justify-between border-b border-border bg-muted;
|
||||||
}
|
}
|
||||||
.header-start-display {
|
.header-start-display {
|
||||||
@apply flex w-[30%] items-center justify-start gap-2;
|
@apply flex items-center justify-start gap-2;
|
||||||
}
|
}
|
||||||
.header-end-division {
|
.header-end-division {
|
||||||
@apply flex w-[30%] justify-end px-2;
|
@apply flex justify-end px-2;
|
||||||
}
|
}
|
||||||
.header-end-display {
|
.header-end-display {
|
||||||
@apply ml-auto mr-2 flex items-center gap-5;
|
@apply ml-auto mr-2 flex items-center gap-5;
|
||||||
|
|
@ -535,7 +535,7 @@
|
||||||
@apply hover:bg-accent hover:text-accent-foreground;
|
@apply hover:bg-accent hover:text-accent-foreground;
|
||||||
}
|
}
|
||||||
.header-github-display {
|
.header-github-display {
|
||||||
@apply -mr-px ml-2 flex h-9 items-center justify-center rounded-md rounded-l-none border bg-background px-2 text-sm;
|
@apply -mr-px ml-1 flex h-9 items-center justify-center rounded-md rounded-l-none border bg-background px-2 text-sm;
|
||||||
}
|
}
|
||||||
.header-notifications-box {
|
.header-notifications-box {
|
||||||
@apply fixed left-0 top-0 h-screen w-screen;
|
@apply fixed left-0 top-0 h-screen w-screen;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { FlowType } from "../flow";
|
|
||||||
|
|
||||||
export type storeContextType = {
|
export type storeContextType = {
|
||||||
savedFlows: { [key: string]: FlowType };
|
savedFlows: Set<string>;
|
||||||
setSavedFlows: (newState: { [key: string]: FlowType }) => void;
|
setSavedFlows: (newState: Set<string>) => void;
|
||||||
setHasStore: (store: boolean) => void;
|
setHasStore: (store: boolean) => void;
|
||||||
hasStore: boolean;
|
hasStore: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export type TabsContextType = {
|
||||||
setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void;
|
setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void;
|
||||||
setTweak: (tweak: tweakType) => tweakType | void;
|
setTweak: (tweak: tweakType) => tweakType | void;
|
||||||
getTweak: tweakType;
|
getTweak: tweakType;
|
||||||
saveComponent: (component: NodeDataType) => void;
|
saveComponent: (component: NodeDataType) => Promise<String | undefined>;
|
||||||
deleteComponent: (id: string, key: string) => void;
|
deleteComponent: (id: string, key: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1009,7 +1009,8 @@ export function expandGroupNode(
|
||||||
gNodes[nodeIndex].data.node!.template[field].show = show;
|
gNodes[nodeIndex].data.node!.template[field].show = show;
|
||||||
gNodes[nodeIndex].data.node!.template[field].advanced = advanced;
|
gNodes[nodeIndex].data.node!.template[field].advanced = advanced;
|
||||||
gNodes[nodeIndex].data.node!.template[field].display_name = display_name;
|
gNodes[nodeIndex].data.node!.template[field].display_name = display_name;
|
||||||
gNodes[nodeIndex].selected = false;
|
// keep the nodes selected after ungrouping
|
||||||
|
// gNodes[nodeIndex].selected = false;
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
gNodes[nodeIndex].data.node!.template[field].proxy = proxy;
|
gNodes[nodeIndex].data.node!.template[field].proxy = proxy;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1096,3 +1097,8 @@ export function downloadNode(NodeFLow: FlowType) {
|
||||||
element.download = `${NodeFLow.name}.json`;
|
element.download = `${NodeFLow.name}.json`;
|
||||||
element.click();
|
element.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateComponentNameAndType(
|
||||||
|
data: any,
|
||||||
|
component: NodeDataType
|
||||||
|
) {}
|
||||||
|
|
|
||||||
|
|
@ -571,7 +571,7 @@ export function IncrementObjectKey(
|
||||||
|
|
||||||
export function removeCountFromString(input: string): string {
|
export function removeCountFromString(input: string): string {
|
||||||
// Define a regex pattern to match the count in parentheses
|
// Define a regex pattern to match the count in parentheses
|
||||||
const pattern = /\s*\(\d+\)\s*$/;
|
const pattern = /\s*\(\w+\)\s*$/;
|
||||||
|
|
||||||
// Use the `replace` method to remove the matched pattern
|
// Use the `replace` method to remove the matched pattern
|
||||||
const result = input.replace(pattern, "");
|
const result = input.replace(pattern, "");
|
||||||
|
|
@ -579,9 +579,13 @@ export function removeCountFromString(input: string): string {
|
||||||
return result.trim(); // Trim any leading/trailing spaces
|
return result.trim(); // Trim any leading/trailing spaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createRandomKey(key: string, uid: string): string {
|
||||||
|
return removeCountFromString(key) + ` (${uid})`;
|
||||||
|
}
|
||||||
|
|
||||||
export function sensitiveSort(a: string, b: string): number {
|
export function sensitiveSort(a: string, b: string): number {
|
||||||
// Extract the name and number from each string using regular expressions
|
// Extract the name and number from each string using regular expressions
|
||||||
const regex = /(.+) \((\d+)\)/;
|
const regex = /(.+) \((\w+)\)/;
|
||||||
const matchA = a.match(regex);
|
const matchA = a.match(regex);
|
||||||
const matchB = b.match(regex);
|
const matchB = b.match(regex);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue