refactor: modular navigation and custom api calls (#3613)

* Added customization files

* Added custom API headers and changed logout mutation

* Changed mutationLogout on authGuard

* Renamed feature flags

* Changed auto login to run after primary loading

* Removed unused code

* Added undefined on mutationfunctiontype to have options

* Changed logout to mutationLogout

* Return data on getConfig

* Added custom wrapper

* changed hasStore to be the constant by default

* Added custom header

* Added customParam feature flag

* Fix routes and custom link and navigate to consider absolute and relative paths

* Fixed regex on  tabs search component

* Added CustomNavigate

* Added basename and ports and URLs config

* [autofix.ci] apply automated fixes

* Update config-constants.ts

* Update index.tsx

* Fixed path comparations

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Lucas Oliveira 2024-08-29 12:05:23 -03:00 • committed by GitHub
commit 06ea6c408b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 300 additions and 215 deletions

View file

@ -1,9 +0,0 @@
{
"ENABLE_DARK_MODE": true,
"ENABLE_API": true,
"ENABLE_LANGFLOW_STORE": true,
"ENABLE_PROFILE_ICONS": true,
"ENABLE_SOCIAL_LINKS": true,
"ENABLE_BRANDING": true,
"ENABLE_MVPS": false
}

View file

@ -1,6 +1,7 @@
import { defineConfig, devices } from "@playwright/test"; import { defineConfig, devices } from "@playwright/test";
import * as dotenv from "dotenv"; import * as dotenv from "dotenv";
import path from "path"; import path from "path";
import { PORT } from "./src/customization/config-constants";
dotenv.config(); dotenv.config();
dotenv.config({ path: path.resolve(__dirname, "../../.env") }); dotenv.config({ path: path.resolve(__dirname, "../../.env") });
@ -31,7 +32,7 @@ export default defineConfig({
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: { use: {
/* Base URL to use in actions like `await page.goto('/')`. */ /* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://localhost:3000/", baseURL: `http://localhost:${PORT || 3000}/`,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry", trace: "on-first-retry",
@ -85,7 +86,7 @@ export default defineConfig({
}, },
{ {
command: "npm start", command: "npm start",
port: 3000, port: PORT || 3000,
env: { env: {
VITE_PROXY_TARGET: "http://127.0.0.1:7860", VITE_PROXY_TARGET: "http://127.0.0.1:7860",
}, },

View file

@ -1,6 +1,6 @@
import { CustomLink } from "@/customization/components/custom-link";
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router-dom";
import IconComponent from "../../../../components/genericIconComponent"; import IconComponent from "../../../../components/genericIconComponent";
import { SingleAlertComponentType } from "../../../../types/alerts"; import { SingleAlertComponentType } from "../../../../types/alerts";
@ -93,12 +93,12 @@ export default function SingleAlert({
</p> </p>
<p className="mt-3 text-sm md:ml-6 md:mt-0"> <p className="mt-3 text-sm md:ml-6 md:mt-0">
{dropItem.link ? ( {dropItem.link ? (
<Link <CustomLink
to={dropItem.link} to={dropItem.link}
className="whitespace-nowrap font-medium text-info-foreground hover:text-accent-foreground" className="whitespace-nowrap font-medium text-info-foreground hover:text-accent-foreground"
> >
Details Details
</Link> </CustomLink>
) : ( ) : (
<></> <></>
)} )}

View file

@ -1,6 +1,6 @@
import { CustomLink } from "@/customization/components/custom-link";
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import { NoticeAlertType } from "../../types/alerts"; import { NoticeAlertType } from "../../types/alerts";
@ -52,12 +52,12 @@ export default function NoticeAlert({
</p> </p>
<p className="mt-3 text-sm md:ml-6 md:mt-0"> <p className="mt-3 text-sm md:ml-6 md:mt-0">
{link !== "" ? ( {link !== "" ? (
<Link <CustomLink
to={link} to={link}
className="whitespace-nowrap font-medium text-info-foreground hover:text-accent-foreground" className="whitespace-nowrap font-medium text-info-foreground hover:text-accent-foreground"
> >
Details Details
</Link> </CustomLink>
) : ( ) : (
<></> <></>
)} )}

View file

@ -1,7 +1,7 @@
import { CustomNavigate } from "@/customization/components/custom-navigate";
import { LoadingPage } from "@/pages/LoadingPage"; import { LoadingPage } from "@/pages/LoadingPage";
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
import { useContext } from "react"; import { useContext } from "react";
import { Navigate } from "react-router-dom";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
export const ProtectedAdminRoute = ({ children }) => { export const ProtectedAdminRoute = ({ children }) => {
@ -13,7 +13,7 @@ export const ProtectedAdminRoute = ({ children }) => {
if (!isAuthenticated) { if (!isAuthenticated) {
return <LoadingPage />; return <LoadingPage />;
} else if ((userData && !isAdmin) || autoLogin) { } else if ((userData && !isAdmin) || autoLogin) {
return <Navigate to="/" replace />; return <CustomNavigate to="/" replace />;
} else { } else {
return children; return children;
} }

View file

@ -3,7 +3,10 @@ import {
LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV, LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV,
LANGFLOW_AUTO_LOGIN_OPTION, LANGFLOW_AUTO_LOGIN_OPTION,
} from "@/constants/constants"; } from "@/constants/constants";
import { useRefreshAccessToken } from "@/controllers/API/queries/auth"; import {
useLogout,
useRefreshAccessToken,
} from "@/controllers/API/queries/auth";
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
import { useEffect } from "react"; import { useEffect } from "react";
import { Cookies } from "react-cookie"; import { Cookies } from "react-cookie";
@ -11,11 +14,11 @@ import { Cookies } from "react-cookie";
export const ProtectedRoute = ({ children }) => { export const ProtectedRoute = ({ children }) => {
const isAuthenticated = useAuthStore((state) => state.isAuthenticated); const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const hasToken = !!localStorage.getItem(LANGFLOW_AUTO_LOGIN_OPTION); const hasToken = !!localStorage.getItem(LANGFLOW_AUTO_LOGIN_OPTION);
const logout = useAuthStore((state) => state.logout);
const cookies = new Cookies(); const cookies = new Cookies();
const refreshToken = cookies.get("refresh_token"); const refreshToken = cookies.get("refresh_token");
const { mutate: mutateRefresh } = useRefreshAccessToken(); const { mutate: mutateRefresh } = useRefreshAccessToken();
const { mutate: mutationLogout } = useLogout();
useEffect(() => { useEffect(() => {
const envRefreshTime = LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV; const envRefreshTime = LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV;
@ -38,7 +41,7 @@ export const ProtectedRoute = ({ children }) => {
}, [isAuthenticated]); }, [isAuthenticated]);
if (!isAuthenticated && hasToken) { if (!isAuthenticated && hasToken) {
logout(); mutationLogout();
} else { } else {
return children; return children;
} }

View file

@ -1,18 +1,16 @@
import { CustomNavigate } from "@/customization/components/custom-navigate";
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
import { Navigate } from "react-router-dom";
export const ProtectedLoginRoute = ({ children }) => { export const ProtectedLoginRoute = ({ children }) => {
const autoLogin = useAuthStore((state) => state.autoLogin); const autoLogin = useAuthStore((state) => state.autoLogin);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated); const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
if (autoLogin === true) { if (autoLogin === true) {
window.location.replace("/"); return <CustomNavigate to="/" replace />;
return <Navigate to="/" replace />;
} }
if (isAuthenticated) { if (isAuthenticated) {
window.location.replace("/"); return <CustomNavigate to="/" replace />;
return <Navigate to="/" replace />;
} }
return children; return children;

View file

@ -1,19 +1,18 @@
import FeatureFlags from "@/../feature-config.json"; import { CustomNavigate } from "@/customization/components/custom-navigate";
import { ENABLE_PROFILE_ICONS } from "@/customization/feature-flags";
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
import { useStoreStore } from "@/stores/storeStore"; import { useStoreStore } from "@/stores/storeStore";
import { Navigate } from "react-router-dom";
export const AuthSettingsGuard = ({ children }) => { export const AuthSettingsGuard = ({ children }) => {
const autoLogin = useAuthStore((state) => state.autoLogin); const autoLogin = useAuthStore((state) => state.autoLogin);
const hasStore = useStoreStore((state) => state.hasStore); const hasStore = useStoreStore((state) => state.hasStore);
// Hides the General settings if there is nothing to show // Hides the General settings if there is nothing to show
const showGeneralSettings = const showGeneralSettings = ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
if (showGeneralSettings) { if (showGeneralSettings) {
return children; return children;
} else { } else {
return <Navigate replace to="global-variables" />; return <CustomNavigate replace to="global-variables" />;
} }
}; };

View file

@ -1,8 +1,8 @@
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { useEffect } from "react"; import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
export const CatchAllRoute = () => { export const CatchAllRoute = () => {
const navigate = useNavigate(); const navigate = useCustomNavigate();
// Redirect to the root ("/") when the catch-all route is matched // Redirect to the root ("/") when the catch-all route is matched
useEffect(() => { useEffect(() => {

View file

@ -1,4 +1,4 @@
import FeatureFlags from "@/../feature-config.json"; import { ENABLE_API } from "@/customization/feature-flags";
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook"; import { useHotkeys } from "react-hotkeys-hook";
@ -138,7 +138,7 @@ export default function FlowToolbar(): JSX.Element {
<div> <div>
<Separator orientation="vertical" /> <Separator orientation="vertical" />
</div> </div>
{FeatureFlags.ENABLE_API && ( {ENABLE_API && (
<> <>
<div className="flex cursor-pointer items-center gap-2"> <div className="flex cursor-pointer items-center gap-2">
{currentFlow && currentFlow.data && ( {currentFlow && currentFlow.data && (

View file

@ -4,6 +4,7 @@ import {
usePostUploadFolders, usePostUploadFolders,
} from "@/controllers/API/queries/folders"; } from "@/controllers/API/queries/folders";
import { useGetDownloadFolders } from "@/controllers/API/queries/folders/use-get-download-folders"; import { useGetDownloadFolders } from "@/controllers/API/queries/folders/use-get-download-folders";
import { ENABLE_CUSTOM_PARAM } from "@/customization/feature-flags";
import { createFileUpload } from "@/helpers/create-file-upload"; import { createFileUpload } from "@/helpers/create-file-upload";
import { getObjectsFromFilelist } from "@/helpers/get-objects-from-filelist"; import { getObjectsFromFilelist } from "@/helpers/get-objects-from-filelist";
import useUploadFlow from "@/hooks/flows/use-upload-flow"; import useUploadFlow from "@/hooks/flows/use-upload-flow";
@ -44,7 +45,8 @@ const SideBarFoldersButtonsComponent = ({
folders.map((obj) => ({ name: obj.name, edit: false })) ?? [], folders.map((obj) => ({ name: obj.name, edit: false })) ?? [],
); );
const currentFolder = pathname.split("/"); const currentFolder = pathname.split("/");
const urlWithoutPath = pathname.split("/").length < 4; const urlWithoutPath =
pathname.split("/").length < (ENABLE_CUSTOM_PARAM ? 5 : 4);
const myCollectionId = useFolderStore((state) => state.myCollectionId); const myCollectionId = useFolderStore((state) => state.myCollectionId);
const folderIdDragging = useFolderStore((state) => state.folderIdDragging); const folderIdDragging = useFolderStore((state) => state.folderIdDragging);

View file

@ -7,12 +7,12 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from "../../../ui/dropdown-menu"; } from "../../../ui/dropdown-menu";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useAddFlow from "@/hooks/flows/use-add-flow"; import useAddFlow from "@/hooks/flows/use-add-flow";
import useSaveFlow from "@/hooks/flows/use-save-flow"; import useSaveFlow from "@/hooks/flows/use-save-flow";
import useUploadFlow from "@/hooks/flows/use-upload-flow"; import useUploadFlow from "@/hooks/flows/use-upload-flow";
import { customStringify } from "@/utils/reactflowUtils"; import { customStringify } from "@/utils/reactflowUtils";
import { useHotkeys } from "react-hotkeys-hook"; import { useHotkeys } from "react-hotkeys-hook";
import { useNavigate } from "react-router-dom";
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants"; import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
import { SAVED_HOVER } from "../../../../constants/constants"; import { SAVED_HOVER } from "../../../../constants/constants";
import ExportModal from "../../../../modals/exportModal"; import ExportModal from "../../../../modals/exportModal";
@ -40,7 +40,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
const [openSettings, setOpenSettings] = useState(false); const [openSettings, setOpenSettings] = useState(false);
const [openLogs, setOpenLogs] = useState(false); const [openLogs, setOpenLogs] = useState(false);
const uploadFlow = useUploadFlow(); const uploadFlow = useUploadFlow();
const navigate = useNavigate(); const navigate = useCustomNavigate();
const isBuilding = useFlowStore((state) => state.isBuilding); const isBuilding = useFlowStore((state) => state.isBuilding);
const getTypes = useTypesStore((state) => state.getTypes); const getTypes = useTypesStore((state) => state.getTypes);
const saveFlow = useSaveFlow(); const saveFlow = useSaveFlow();

View file

@ -1,7 +1,7 @@
import { useContext } from "react"; import { useContext } from "react";
import { FaDiscord, FaGithub } from "react-icons/fa"; import { FaDiscord, FaGithub } from "react-icons/fa";
import { RiTwitterXFill } from "react-icons/ri"; import { RiTwitterXFill } from "react-icons/ri";
import { Link, useLocation, useNavigate } from "react-router-dom"; import { useLocation } from "react-router-dom";
import AlertDropdown from "../../alerts/alertDropDown"; import AlertDropdown from "../../alerts/alertDropDown";
import { import {
BASE_URL_API, BASE_URL_API,
@ -10,8 +10,14 @@ import {
} from "../../constants/constants"; } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import FeatureFlags from "@/../feature-config.json";
import { useLogout } from "@/controllers/API/queries/auth"; import { useLogout } from "@/controllers/API/queries/auth";
import { CustomLink } from "@/customization/components/custom-link";
import {
ENABLE_DARK_MODE,
ENABLE_PROFILE_ICONS,
ENABLE_SOCIAL_LINKS,
} from "@/customization/feature-flags";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore"; import { useDarkStore } from "../../stores/darkStore";
@ -38,9 +44,8 @@ export default function Header(): JSX.Element {
const autoLogin = useAuthStore((state) => state.autoLogin); const autoLogin = useAuthStore((state) => state.autoLogin);
const { mutate: mutationLogout } = useLogout(); const { mutate: mutationLogout } = useLogout();
const logout = useAuthStore((state) => state.logout);
const navigate = useNavigate(); const navigate = useCustomNavigate();
const hasStore = useStoreStore((state) => state.hasStore); const hasStore = useStoreStore((state) => state.hasStore);
const dark = useDarkStore((state) => state.dark); const dark = useDarkStore((state) => state.dark);
@ -65,22 +70,15 @@ export default function Header(): JSX.Element {
); );
const handleLogout = () => { const handleLogout = () => {
mutationLogout(undefined, { mutationLogout();
onSuccess: () => {
logout();
},
onError: (error) => {
console.error(error);
},
});
}; };
return ( return (
<div className="header-arrangement relative"> <div className="header-arrangement relative">
<div className="header-start-display"> <div className="header-start-display">
<Link to="/all" className="cursor-pointer"> <CustomLink to="/all" className="cursor-pointer">
<span className="ml-4 text-2xl">⛓️</span> <span className="ml-4 text-2xl">⛓️</span>
</Link> </CustomLink>
{showArrowReturnIcon && ( {showArrowReturnIcon && (
<Button <Button
unstyled unstyled
@ -96,12 +94,12 @@ export default function Header(): JSX.Element {
</div> </div>
<div className="flex items-center xl:absolute xl:left-1/2 xl:-translate-x-1/2"> <div className="flex items-center xl:absolute xl:left-1/2 xl:-translate-x-1/2">
<Link to="/all"> <CustomLink to="/all">
<Button <Button
className="gap-2" className="gap-2"
variant={ variant={
location.pathname === "/all" || location.pathname.includes("/all") ||
location.pathname === "/components" location.pathname.includes("/components")
? "primary" ? "primary"
: "secondary" : "secondary"
} }
@ -110,25 +108,27 @@ export default function Header(): JSX.Element {
<IconComponent name="Home" className="h-4 w-4" /> <IconComponent name="Home" className="h-4 w-4" />
<div className="hidden flex-1 lg:block">{USER_PROJECTS_HEADER}</div> <div className="hidden flex-1 lg:block">{USER_PROJECTS_HEADER}</div>
</Button> </Button>
</Link> </CustomLink>
{hasStore && ( {hasStore && (
<Link to="/store"> <CustomLink to="/store">
<Button <Button
className="gap-2" className="gap-2"
variant={location.pathname === "/store" ? "primary" : "secondary"} variant={
location.pathname.includes("/store") ? "primary" : "secondary"
}
size="sm" size="sm"
data-testid="button-store" data-testid="button-store"
> >
<IconComponent name="Store" className="h-4 w-4" /> <IconComponent name="Store" className="h-4 w-4" />
<div className="hidden flex-1 lg:block">Store</div> <div className="hidden flex-1 lg:block">Store</div>
</Button> </Button>
</Link> </CustomLink>
)} )}
</div> </div>
<div className="header-end-division"> <div className="header-end-division">
<div className="header-end-display"> <div className="header-end-display">
{FeatureFlags.ENABLE_SOCIAL_LINKS && ( {ENABLE_SOCIAL_LINKS && (
<> <>
<a <a
href="https://github.com/langflow-ai/langflow" href="https://github.com/langflow-ai/langflow"
@ -160,7 +160,7 @@ export default function Header(): JSX.Element {
<Separator orientation="vertical" /> <Separator orientation="vertical" />
</> </>
)} )}
{FeatureFlags.ENABLE_DARK_MODE && ( {ENABLE_DARK_MODE && (
<button <button
className="extra-side-bar-save-disable" className="extra-side-bar-save-disable"
onClick={() => { onClick={() => {
@ -202,7 +202,7 @@ export default function Header(): JSX.Element {
data-testid="user-profile-settings" data-testid="user-profile-settings"
className="shrink-0" className="shrink-0"
> >
{FeatureFlags.ENABLE_PROFILE_ICONS ? ( {ENABLE_PROFILE_ICONS ? (
<img <img
src={profileImageUrl} src={profileImageUrl}
className="h-7 w-7 shrink-0 focus-visible:outline-0" className="h-7 w-7 shrink-0 focus-visible:outline-0"

View file

@ -1,4 +1,4 @@
import { Link } from "react-router-dom"; import { CustomLink } from "@/customization/components/custom-link";
import { cn } from "../../../../utils/utils"; import { cn } from "../../../../utils/utils";
import { buttonVariants } from "../../../ui/button"; import { buttonVariants } from "../../../ui/button";
@ -18,13 +18,13 @@ const SideBarButtonsComponent = ({
return ( return (
<div className="flex gap-2 overflow-auto lg:h-[70vh] lg:flex-col"> <div className="flex gap-2 overflow-auto lg:h-[70vh] lg:flex-col">
{items.map((item) => ( {items.map((item) => (
<Link to={item.href!}> <CustomLink to={item.href!}>
<div <div
key={item.title} key={item.title}
data-testid={`sidebar-nav-${item.title}`} data-testid={`sidebar-nav-${item.title}`}
className={cn( className={cn(
buttonVariants({ variant: "ghost" }), buttonVariants({ variant: "ghost" }),
pathname === item.href item.href && pathname.endsWith(item.href)
? "border border-border bg-muted hover:bg-muted" ? "border border-border bg-muted hover:bg-muted"
: "border border-transparent hover:border-border hover:bg-transparent", : "border border-transparent hover:border-border hover:bg-transparent",
"flex w-full shrink-0 justify-start gap-4", "flex w-full shrink-0 justify-start gap-4",
@ -35,7 +35,7 @@ const SideBarButtonsComponent = ({
{item.title} {item.title}
</span> </span>
</div> </div>
</Link> </CustomLink>
))} ))}
</div> </div>
); );

View file

@ -1,11 +1,11 @@
import { Navigate } from "react-router-dom"; import { CustomNavigate } from "@/customization/components/custom-navigate";
import { useStoreStore } from "../../stores/storeStore"; import { useStoreStore } from "../../stores/storeStore";
export const StoreGuard = ({ children }) => { export const StoreGuard = ({ children }) => {
const hasStore = useStoreStore((state) => state.hasStore); const hasStore = useStoreStore((state) => state.hasStore);
if (!hasStore) { if (!hasStore) {
return <Navigate to="/all" replace />; return <CustomNavigate to="/all" replace />;
} }
return children; return children;

View file

@ -1,5 +1,6 @@
// src/constants/constants.ts // src/constants/constants.ts
import custom from "../customization/config-constants";
import { languageMap } from "../types/components"; import { languageMap } from "../types/components";
/** /**
@ -567,9 +568,7 @@ export const ADMIN_HEADER_TITLE = "Admin Page";
export const ADMIN_HEADER_DESCRIPTION = export const ADMIN_HEADER_DESCRIPTION =
"Navigate through this section to efficiently oversee all application users. From here, you can seamlessly manage user accounts."; "Navigate through this section to efficiently oversee all application users. From here, you can seamlessly manage user accounts.";
export const BASE_URL_API = "/api/v1/"; export const BASE_URL_API = custom.BASE_URL_API || "/api/v1/";
export const BACKEND_URL = process.env.BACKEND_URL || "http://localhost:7860/";
/** /**
* URLs excluded from error retries. * URLs excluded from error retries.
@ -580,7 +579,7 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
`${BASE_URL_API}validate/code`, `${BASE_URL_API}validate/code`,
`${BASE_URL_API}custom_component`, `${BASE_URL_API}custom_component`,
`${BASE_URL_API}validate/prompt`, `${BASE_URL_API}validate/prompt`,
`http://localhost:7860/login`, `${BASE_URL_API}/login`,
`${BASE_URL_API}api_key/store`, `${BASE_URL_API}api_key/store`,
]; ];

View file

@ -1,3 +1,4 @@
import { CustomWrapper } from "@/customization/custom-wrapper";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactNode } from "react"; import { ReactNode } from "react";
import { ReactFlowProvider } from "reactflow"; import { ReactFlowProvider } from "reactflow";
@ -10,6 +11,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
//element to wrap all context //element to wrap all context
return ( return (
<> <>
<CustomWrapper>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<AuthProvider> <AuthProvider>
<TooltipProvider skipDelayDuration={0}> <TooltipProvider skipDelayDuration={0}>
@ -20,6 +22,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
</TooltipProvider> </TooltipProvider>
</AuthProvider> </AuthProvider>
</QueryClientProvider> </QueryClientProvider>
</CustomWrapper>
</> </>
); );
} }

View file

@ -1,4 +1,5 @@
import { LANGFLOW_ACCESS_TOKEN } from "@/constants/constants"; import { LANGFLOW_ACCESS_TOKEN } from "@/constants/constants";
import { useCustomApiHeaders } from "@/customization/hooks/use-custom-api-headers";
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from "axios"; import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from "axios";
import { useContext, useEffect } from "react"; import { useContext, useEffect } from "react";
@ -22,8 +23,8 @@ function ApiInterceptor() {
let { accessToken, authenticationErrorCount } = useContext(AuthContext); let { accessToken, authenticationErrorCount } = useContext(AuthContext);
const { mutate: mutationLogout } = useLogout(); const { mutate: mutationLogout } = useLogout();
const { mutate: mutationRenewAccessToken } = useRefreshAccessToken(); const { mutate: mutationRenewAccessToken } = useRefreshAccessToken();
const logout = useAuthStore((state) => state.logout);
const isLoginPage = location.pathname.includes("login"); const isLoginPage = location.pathname.includes("login");
const customHeaders = useCustomApiHeaders();
useEffect(() => { useEffect(() => {
const interceptor = api.interceptors.response.use( const interceptor = api.interceptors.response.use(
@ -102,6 +103,10 @@ function ApiInterceptor() {
config.headers["Authorization"] = `Bearer ${accessToken}`; config.headers["Authorization"] = `Bearer ${accessToken}`;
} }
for (const [key, value] of Object.entries(customHeaders)) {
config.headers[key] = value;
}
return { return {
...config, ...config,
signal: controller.signal, signal: controller.signal,
@ -117,7 +122,7 @@ function ApiInterceptor() {
api.interceptors.response.eject(interceptor); api.interceptors.response.eject(interceptor);
api.interceptors.request.eject(requestInterceptor); api.interceptors.request.eject(requestInterceptor);
}; };
}, [accessToken, setErrorData]); }, [accessToken, setErrorData, customHeaders]);
function checkErrorCount() { function checkErrorCount() {
if (isLoginPage) return; if (isLoginPage) return;
@ -126,14 +131,7 @@ function ApiInterceptor() {
if (authenticationErrorCount > 3) { if (authenticationErrorCount > 3) {
authenticationErrorCount = 0; authenticationErrorCount = 0;
mutationLogout(undefined, { mutationLogout();
onSuccess: () => {
logout();
},
onError: (error) => {
console.error(error);
},
});
return false; return false;
} }
@ -142,6 +140,11 @@ function ApiInterceptor() {
async function tryToRenewAccessToken(error: AxiosError) { async function tryToRenewAccessToken(error: AxiosError) {
if (isLoginPage) return; if (isLoginPage) return;
if (error.config?.headers) {
for (const [key, value] of Object.entries(customHeaders)) {
error.config.headers[key] = value;
}
}
mutationRenewAccessToken( mutationRenewAccessToken(
{}, {},
{ {
@ -152,14 +155,7 @@ function ApiInterceptor() {
}, },
onError: (error) => { onError: (error) => {
console.error(error); console.error(error);
mutationLogout(undefined, { mutationLogout();
onSuccess: () => {
logout();
},
onError: (error) => {
console.error(error);
},
});
return Promise.reject("Authentication error"); return Promise.reject("Authentication error");
}, },
}, },

View file

@ -8,9 +8,10 @@ interface IDeleteApiKey {
} }
// add types for error handling and success // add types for error handling and success
export const useDeleteApiKey: useMutationFunctionType<IDeleteApiKey> = ( export const useDeleteApiKey: useMutationFunctionType<
options, undefined,
) => { IDeleteApiKey
> = (options) => {
const { mutate } = UseRequestProcessor(); const { mutate } = UseRequestProcessor();
const deleteApiKeyFn = async (payload: IDeleteApiKey): Promise<any> => { const deleteApiKeyFn = async (payload: IDeleteApiKey): Promise<any> => {

View file

@ -6,6 +6,7 @@ import { useQueryFunctionType, Users } from "../../../../types/api";
import { api } from "../../api"; import { api } from "../../api";
import { getURL } from "../../helpers/constants"; import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor"; import { UseRequestProcessor } from "../../services/request-processor";
import { useLogout } from "./use-post-logout";
export interface AutoLoginResponse { export interface AutoLoginResponse {
frontend_timeout: number; frontend_timeout: number;
@ -21,8 +22,8 @@ export const useGetAutoLogin: useQueryFunctionType<undefined, undefined> = (
const { login, setUserData, getUser } = useContext(AuthContext); const { login, setUserData, getUser } = useContext(AuthContext);
const setAutoLogin = useAuthStore((state) => state.setAutoLogin); const setAutoLogin = useAuthStore((state) => state.setAutoLogin);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated); const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const logout = useAuthStore((state) => state.logout);
const isLoginPage = location.pathname.includes("login"); const isLoginPage = location.pathname.includes("login");
const { mutate: mutationLogout } = useLogout();
async function getAutoLoginFn(): Promise<null> { async function getAutoLoginFn(): Promise<null> {
try { try {
@ -40,7 +41,7 @@ export const useGetAutoLogin: useQueryFunctionType<undefined, undefined> = (
setAutoLogin(false); setAutoLogin(false);
if (!isLoginPage) { if (!isLoginPage) {
if (!isAuthenticated) { if (!isAuthenticated) {
await logout(); mutationLogout();
throw new Error("Unauthorized"); throw new Error("Unauthorized");
} else { } else {
getUser(); getUser();

View file

@ -1,6 +1,7 @@
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
import { useMutationFunctionType } from "@/types/api"; import { useMutationFunctionType } from "@/types/api";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { api } from "../../api"; import { api } from "../../api";
import { getURL } from "../../helpers/constants"; import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor"; import { UseRequestProcessor } from "../../services/request-processor";
@ -9,6 +10,7 @@ export const useLogout: useMutationFunctionType<undefined, void> = (
options?, options?,
) => { ) => {
const { mutate } = UseRequestProcessor(); const { mutate } = UseRequestProcessor();
const navigate = useCustomNavigate();
const logout = useAuthStore((state) => state.logout); const logout = useAuthStore((state) => state.logout);
async function logoutUser(): Promise<any> { async function logoutUser(): Promise<any> {
@ -23,6 +25,7 @@ export const useLogout: useMutationFunctionType<undefined, void> = (
const mutation = mutate(["useLogout"], logoutUser, { const mutation = mutate(["useLogout"], logoutUser, {
onSuccess: () => { onSuccess: () => {
logout(); logout();
navigate("/login");
}, },
onError: (error) => { onError: (error) => {
console.error(error); console.error(error);

View file

@ -12,7 +12,7 @@ export interface ConfigResponse {
health_check_max_retries: number; health_check_max_retries: number;
} }
export const useGetConfig: useQueryFunctionType<undefined, undefined> = ( export const useGetConfig: useQueryFunctionType<undefined, ConfigResponse> = (
options, options,
) => { ) => {
const setAutoSaving = useFlowsManagerStore((state) => state.setAutoSaving); const setAutoSaving = useFlowsManagerStore((state) => state.setAutoSaving);
@ -38,6 +38,7 @@ export const useGetConfig: useQueryFunctionType<undefined, undefined> = (
setAutoSavingInterval(data.auto_saving_interval); setAutoSavingInterval(data.auto_saving_interval);
setHealthCheckMaxRetries(data.health_check_max_retries); setHealthCheckMaxRetries(data.health_check_max_retries);
} }
return data;
}; };
const queryResult = query(["useGetConfig"], getConfigFn, options); const queryResult = query(["useGetConfig"], getConfigFn, options);

View file

@ -1,32 +0,0 @@
import axios from "axios";
import { BASE_URL_API } from "../../constants/constants";
/**
* Fetches the configuration data from the API.
* @returns {Promise<any>} A promise that resolves to the configuration data.
* @throws {Error} If there was an error fetching the configuration data.
*/
export async function fetchConfig() {
try {
const response = await axios.get(`${BASE_URL_API}config`);
return response.data;
} catch (error) {
console.error("Failed to fetch configuration:", error);
throw error;
}
}
/**
* Sets up default configurations for Axios.
* Fetches the timeout configuration and sets it as the default timeout for Axios requests.
*/
export async function setupAxiosDefaults() {
const config = await fetchConfig();
// Create Axios instance with the fetched timeout configuration
const timeoutInMilliseconds = config.frontend_timeout
? config.frontend_timeout * 1000
: 30000;
axios.defaults.baseURL = "";
axios.defaults.timeout = timeoutInMilliseconds;
}

View file

@ -0,0 +1,3 @@
export function CustomHeader() {
return <></>;
}

View file

@ -0,0 +1,11 @@
import { Link, LinkProps, useParams } from "react-router-dom";
import { ENABLE_CUSTOM_PARAM } from "../feature-flags";
export function CustomLink({ to, ...props }: LinkProps) {
const { customParam } = useParams();
const newLocation =
ENABLE_CUSTOM_PARAM && to[0] === "/" ? `/${customParam}${to}` : to;
return <Link to={newLocation} {...props} />;
}

View file

@ -0,0 +1,10 @@
import { Navigate, NavigateProps, useParams } from "react-router-dom";
import { ENABLE_CUSTOM_PARAM } from "../feature-flags";
export function CustomNavigate({ to, ...props }: NavigateProps) {
const { customParam } = useParams();
const newLocation =
ENABLE_CUSTOM_PARAM && to[0] === "/" ? `/${customParam}${to}` : to;
return <Navigate to={newLocation} {...props} />;
}

View file

@ -0,0 +1,13 @@
export const BASENAME = "";
export const PORT = 3000;
export const PROXY_TARGET = "http://127.0.0.1:7860";
export const API_ROUTES = ["^/api/v1/", "/health"];
export const BASE_URL_API = "/api/v1/";
export default {
BASENAME,
PORT,
PROXY_TARGET,
API_ROUTES,
BASE_URL_API,
};

View file

@ -0,0 +1,3 @@
export function CustomWrapper({ children }) {
return children;
}

View file

@ -0,0 +1,8 @@
export const ENABLE_DARK_MODE = true;
export const ENABLE_API = true;
export const ENABLE_LANGFLOW_STORE = true;
export const ENABLE_PROFILE_ICONS = true;
export const ENABLE_SOCIAL_LINKS = true;
export const ENABLE_BRANDING = true;
export const ENABLE_MVPS = false;
export const ENABLE_CUSTOM_PARAM = false;

View file

@ -0,0 +1,5 @@
export function useCustomApiHeaders() {
const customHeaders = {};
return customHeaders;
}

View file

@ -0,0 +1,27 @@
import {
NavigateFunction,
NavigateOptions,
To,
useNavigate,
useParams,
} from "react-router-dom";
import { ENABLE_CUSTOM_PARAM } from "../feature-flags";
export function useCustomNavigate(): NavigateFunction {
const domNavigate = useNavigate();
const { customParam } = useParams();
function navigate(to: To | number, options?: NavigateOptions) {
if (typeof to === "number") {
domNavigate(to);
} else {
domNavigate(
ENABLE_CUSTOM_PARAM && to[0] === "/" ? `/${customParam}${to}` : to,
options,
);
}
}
return navigate;
}

View file

@ -0,0 +1,20 @@
import { UseRequestProcessor } from "@/controllers/API/services/request-processor";
import { useQueryFunctionType } from "@/types/api";
export const usePrimaryLoading: useQueryFunctionType<undefined, null> = (
options,
) => {
const { query } = UseRequestProcessor();
const getPrimaryLoadingFn = async () => {
return null;
};
const queryResult = query(
["usePrimaryLoading"],
getPrimaryLoadingFn,
options,
);
return queryResult;
};

View file

@ -1,8 +1,7 @@
import { useGetDownloadFileMutation } from "@/controllers/API/queries/files"; import { useGetDownloadFileMutation } from "@/controllers/API/queries/files";
import { useState } from "react"; import { useState } from "react";
import { ForwardedIconComponent } from "../../../../../components/genericIconComponent"; import { ForwardedIconComponent } from "../../../../../components/genericIconComponent";
import { BACKEND_URL, BASE_URL_API } from "../../../../../constants/constants"; import { BASE_URL_API } from "../../../../../constants/constants";
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
import { fileCardPropsType } from "../../../../../types/components"; import { fileCardPropsType } from "../../../../../types/components";
import formatFileName from "../filePreviewChat/utils/format-file-name"; import formatFileName from "../filePreviewChat/utils/format-file-name";
import DownloadButton from "./components/downloadButton/downloadButton"; import DownloadButton from "./components/downloadButton/downloadButton";

View file

@ -1,5 +1,6 @@
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useAddFlow from "@/hooks/flows/use-add-flow"; import useAddFlow from "@/hooks/flows/use-add-flow";
import { useNavigate, useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { import {
Card, Card,
CardContent, CardContent,
@ -9,7 +10,7 @@ import {
export default function NewFlowCardComponent() { export default function NewFlowCardComponent() {
const addFlow = useAddFlow(); const addFlow = useAddFlow();
const navigate = useNavigate(); const navigate = useCustomNavigate();
const { folderId } = useParams(); const { folderId } = useParams();
return ( return (

View file

@ -1,5 +1,5 @@
/// <reference types="vite-plugin-svgr/client" /> /// <reference types="vite-plugin-svgr/client" />
import { useNavigate, useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import BlogPost from "../../../../assets/undraw_blog_post_re_fy5x.svg?react"; import BlogPost from "../../../../assets/undraw_blog_post_re_fy5x.svg?react";
import ChatBot from "../../../../assets/undraw_chat_bot_re_e2gj.svg?react"; import ChatBot from "../../../../assets/undraw_chat_bot_re_e2gj.svg?react";
import PromptChaining from "../../../../assets/undraw_cloud_docs_re_xjht.svg?react"; import PromptChaining from "../../../../assets/undraw_cloud_docs_re_xjht.svg?react";
@ -10,6 +10,7 @@ import APIRequest from "../../../../assets/undraw_real_time_analytics_re_yliv.sv
import BasicPrompt from "../../../../assets/undraw_short_bio_re_fmx0.svg?react"; import BasicPrompt from "../../../../assets/undraw_short_bio_re_fmx0.svg?react";
import TransferFiles from "../../../../assets/undraw_transfer_files_re_a2a9.svg?react"; import TransferFiles from "../../../../assets/undraw_transfer_files_re_a2a9.svg?react";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useAddFlow from "@/hooks/flows/use-add-flow"; import useAddFlow from "@/hooks/flows/use-add-flow";
import { import {
Card, Card,
@ -25,7 +26,7 @@ export default function UndrawCardComponent({
flow, flow,
}: UndrawCardComponentProps): JSX.Element { }: UndrawCardComponentProps): JSX.Element {
const addFlow = useAddFlow(); const addFlow = useAddFlow();
const navigate = useNavigate(); const navigate = useCustomNavigate();
const { folderId } = useParams(); const { folderId } = useParams();
const myCollectionId = useFolderStore((state) => state.myCollectionId); const myCollectionId = useFolderStore((state) => state.myCollectionId);

View file

@ -1,6 +1,7 @@
import { useGetAutoLogin } from "@/controllers/API/queries/auth"; import { useGetAutoLogin } from "@/controllers/API/queries/auth";
import { useGetConfig } from "@/controllers/API/queries/config/use-get-config"; import { useGetConfig } from "@/controllers/API/queries/config/use-get-config";
import { useGetVersionQuery } from "@/controllers/API/queries/version"; import { useGetVersionQuery } from "@/controllers/API/queries/version";
import { usePrimaryLoading } from "@/customization/hooks/use-primary-loading";
import { useDarkStore } from "@/stores/darkStore"; import { useDarkStore } from "@/stores/darkStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore"; import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useEffect } from "react"; import { useEffect } from "react";
@ -12,7 +13,8 @@ export function AppInitPage() {
const refreshStars = useDarkStore((state) => state.refreshStars); const refreshStars = useDarkStore((state) => state.refreshStars);
const isLoading = useFlowsManagerStore((state) => state.isLoading); const isLoading = useFlowsManagerStore((state) => state.isLoading);
const { isFetched } = useGetAutoLogin(); const { isFetched: isLoaded } = usePrimaryLoading();
const { isFetched } = useGetAutoLogin({ enabled: isLoaded });
useGetVersionQuery({ enabled: isFetched }); useGetVersionQuery({ enabled: isFetched });
useGetConfig({ enabled: isFetched }); useGetConfig({ enabled: isFetched });

View file

@ -9,6 +9,7 @@ import {
TIMEOUT_ERROR_MESSAGE, TIMEOUT_ERROR_MESSAGE,
} from "@/constants/constants"; } from "@/constants/constants";
import { useGetHealthQuery } from "@/controllers/API/queries/health"; import { useGetHealthQuery } from "@/controllers/API/queries/health";
import { CustomHeader } from "@/customization/components/custom-header";
import useFlowsManagerStore from "@/stores/flowsManagerStore"; import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useUtilityStore } from "@/stores/utilityStore"; import { useUtilityStore } from "@/stores/utilityStore";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
@ -96,6 +97,7 @@ export function AppWrapperPage() {
return ( return (
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
<CustomHeader />
<ErrorBoundary <ErrorBoundary
onReset={() => { onReset={() => {
// any reset function // any reset function
@ -107,7 +109,6 @@ export function AppWrapperPage() {
<Outlet /> <Outlet />
</> </>
</ErrorBoundary> </ErrorBoundary>
<div></div>
<div className="app-div"> <div className="app-div">
<AlertDisplayArea /> <AlertDisplayArea />
</div> </div>

View file

@ -1,4 +1,4 @@
import FeatureFlags from "@/../feature-config.json"; import { ENABLE_MVPS } from "@/customization/feature-flags";
import { useStoreStore } from "@/stores/storeStore"; import { useStoreStore } from "@/stores/storeStore";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@ -193,10 +193,10 @@ export default function ExtraSidebar(): JSX.Element {
// Set search input state // Set search input state
setSearch(event.target.value); setSearch(event.target.value);
}} }}
autocomplete="off" autoComplete="off"
readonly="readonly" readOnly
onClick={() => onClick={() =>
document.getElementById("search").removeAttribute("readonly") document?.getElementById("search")?.removeAttribute("readonly")
} }
/> />
<div <div
@ -298,7 +298,7 @@ export default function ExtraSidebar(): JSX.Element {
</a> </a>
)} )}
</ParentDisclosureComponent> </ParentDisclosureComponent>
{FeatureFlags.ENABLE_MVPS && ( {ENABLE_MVPS && (
<> <>
<Separator /> <Separator />

View file

@ -1,12 +1,13 @@
import FeatureFlags from "@/../feature-config.json";
import { useGetRefreshFlows } from "@/controllers/API/queries/flows/use-get-refresh-flows"; import { useGetRefreshFlows } from "@/controllers/API/queries/flows/use-get-refresh-flows";
import { useGetGlobalVariables } from "@/controllers/API/queries/variables"; import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
import { ENABLE_BRANDING } from "@/customization/feature-flags";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useSaveFlow from "@/hooks/flows/use-save-flow"; import useSaveFlow from "@/hooks/flows/use-save-flow";
import { SaveChangesModal } from "@/modals/saveChangesModal"; import { SaveChangesModal } from "@/modals/saveChangesModal";
import { useTypesStore } from "@/stores/typesStore"; import { useTypesStore } from "@/stores/typesStore";
import { customStringify } from "@/utils/reactflowUtils"; import { customStringify } from "@/utils/reactflowUtils";
import { useEffect } from "react"; import { useEffect } from "react";
import { useBlocker, useNavigate, useParams } from "react-router-dom"; import { useBlocker, useParams } from "react-router-dom";
import FlowToolbar from "../../components/chatComponent"; import FlowToolbar from "../../components/chatComponent";
import { useDarkStore } from "../../stores/darkStore"; import { useDarkStore } from "../../stores/darkStore";
import useFlowStore from "../../stores/flowStore"; import useFlowStore from "../../stores/flowStore";
@ -27,7 +28,7 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
const version = useDarkStore((state) => state.version); const version = useDarkStore((state) => state.version);
const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage); const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
const { id } = useParams(); const { id } = useParams();
const navigate = useNavigate(); const navigate = useCustomNavigate();
useGetGlobalVariables(); useGetGlobalVariables();
const saveFlow = useSaveFlow(); const saveFlow = useSaveFlow();
@ -106,7 +107,7 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
</main> </main>
</div> </div>
)} )}
{FeatureFlags.ENABLE_BRANDING && version && ( {ENABLE_BRANDING && version && (
<a <a
target={"_blank"} target={"_blank"}
href="https://medium.com/logspace/langflow-datastax-better-together-1b7462cebc4d" href="https://medium.com/logspace/langflow-datastax-better-together-1b7462cebc4d"

View file

@ -1,7 +1,7 @@
import { useLoginUser } from "@/controllers/API/queries/auth"; import { useLoginUser } from "@/controllers/API/queries/auth";
import { CustomLink } from "@/customization/components/custom-link";
import * as Form from "@radix-ui/react-form"; import * as Form from "@radix-ui/react-form";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import { Link } from "react-router-dom";
import InputComponent from "../../components/inputComponent"; import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
@ -124,11 +124,11 @@ export default function LoginPage(): JSX.Element {
</Form.Submit> </Form.Submit>
</div> </div>
<div className="w-full"> <div className="w-full">
<Link to="/signup"> <CustomLink to="/signup">
<Button className="w-full" variant="outline" type="button"> <Button className="w-full" variant="outline" type="button">
Don't have an account?&nbsp;<b>Sign Up</b> Don't have an account?&nbsp;<b>Sign Up</b>
</Button> </Button>
</Link> </CustomLink>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,9 +1,8 @@
import { Link, useNavigate, useParams } from "react-router-dom"; import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { useParams } from "react-router-dom";
import CollectionCardComponent from "../../../../../../components/cardComponent"; import CollectionCardComponent from "../../../../../../components/cardComponent";
import IconComponent from "../../../../../../components/genericIconComponent";
import { Button } from "../../../../../../components/ui/button";
const CollectionCard = ({ item, type, isLoading, control }) => { const CollectionCard = ({ item, type, isLoading, control }) => {
const navigate = useNavigate(); const navigate = useCustomNavigate();
const isComponent = item.is_component ?? false; const isComponent = item.is_component ?? false;
const editFlowButtonTestId = `edit-flow-button-${item.id}`; const editFlowButtonTestId = `edit-flow-button-${item.id}`;

View file

@ -1,5 +1,5 @@
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { useEffect } from "react"; import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
type TabsSearchComponentProps = { type TabsSearchComponentProps = {
tabsOptions: string[]; tabsOptions: string[];
@ -14,23 +14,23 @@ const TabsSearchComponent = ({
loading, loading,
tabActive, tabActive,
}: TabsSearchComponentProps) => { }: TabsSearchComponentProps) => {
const navigate = useNavigate(); const navigate = useCustomNavigate();
const changeLocation = (tabOption) => { const changeLocation = (tabOption) => {
const location = window.location.pathname; const location = window.location.pathname;
let newLocation = ""; let newLocation = "";
switch (tabOption) { switch (tabOption) {
case "Flows": case "Flows":
newLocation = location.replace(/components|all/, "flows"); newLocation = location.replace(/.*\/(?:all|components)/, "/flows");
break; break;
case "Components": case "Components":
newLocation = location.replace(/flows|all/, "components"); newLocation = location.replace(/.*\/(?:flows|all)/, "/components");
break; break;
default: default:
newLocation = location.replace(/flows|components/, "all"); newLocation = location.replace(/.*\/(?:flows|components)/, "/all");
break; break;
} }
console.log(newLocation);
navigate(newLocation); navigate(newLocation);
}; };

View file

@ -1,8 +1,9 @@
import FolderSidebarNav from "@/components/folderSidebarComponent"; import FolderSidebarNav from "@/components/folderSidebarComponent";
import { useDeleteFolders } from "@/controllers/API/queries/folders"; import { useDeleteFolders } from "@/controllers/API/queries/folders";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useAlertStore from "@/stores/alertStore"; import useAlertStore from "@/stores/alertStore";
import { useState } from "react"; import { useState } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom"; import { Outlet, useLocation } from "react-router-dom";
import DropdownButton from "../../../../components/dropdownButtonComponent"; import DropdownButton from "../../../../components/dropdownButtonComponent";
import PageLayout from "../../../../components/pageLayout"; import PageLayout from "../../../../components/pageLayout";
import { import {
@ -18,9 +19,9 @@ export default function HomePage(): JSX.Element {
const pathname = location.pathname; const pathname = location.pathname;
const [openModal, setOpenModal] = useState(false); const [openModal, setOpenModal] = useState(false);
const [openDeleteFolderModal, setOpenDeleteFolderModal] = useState(false); const [openDeleteFolderModal, setOpenDeleteFolderModal] = useState(false);
const is_component = pathname === "/components"; const is_component = pathname.includes("/components");
const setFolderToEdit = useFolderStore((state) => state.setFolderToEdit); const setFolderToEdit = useFolderStore((state) => state.setFolderToEdit);
const navigate = useNavigate(); const navigate = useCustomNavigate();
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);

View file

@ -1,9 +1,10 @@
import { useGetRefreshFlows } from "@/controllers/API/queries/flows/use-get-refresh-flows"; import { useGetRefreshFlows } from "@/controllers/API/queries/flows/use-get-refresh-flows";
import { useGetGlobalVariables } from "@/controllers/API/queries/variables"; import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { useStoreStore } from "@/stores/storeStore"; import { useStoreStore } from "@/stores/storeStore";
import { useTypesStore } from "@/stores/typesStore"; import { useTypesStore } from "@/stores/typesStore";
import { useEffect } from "react"; import { useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { getComponent } from "../../controllers/API"; import { getComponent } from "../../controllers/API";
import IOModal from "../../modals/IOModal"; import IOModal from "../../modals/IOModal";
import useFlowsManagerStore from "../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore";
@ -22,7 +23,7 @@ export default function PlaygroundPage() {
return newFlow; return newFlow;
} }
const navigate = useNavigate(); const navigate = useCustomNavigate();
useGetGlobalVariables(); useGetGlobalVariables();
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);

View file

@ -1,4 +1,4 @@
import FeatureFlags from "@/../feature-config.json"; import { ENABLE_PROFILE_ICONS } from "@/customization/feature-flags";
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
import { useStoreStore } from "@/stores/storeStore"; import { useStoreStore } from "@/stores/storeStore";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
@ -11,8 +11,7 @@ export default function SettingsPage(): JSX.Element {
const hasStore = useStoreStore((state) => state.hasStore); const hasStore = useStoreStore((state) => state.hasStore);
// Hides the General settings if there is nothing to show // Hides the General settings if there is nothing to show
const showGeneralSettings = const showGeneralSettings = ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
const sidebarNavItems: { const sidebarNavItems: {
href?: string; href?: string;

View file

@ -1,4 +1,3 @@
import FeatureFlags from "@/../feature-config.json";
import { import {
EDIT_PASSWORD_ALERT_LIST, EDIT_PASSWORD_ALERT_LIST,
EDIT_PASSWORD_ERROR_ALERT, EDIT_PASSWORD_ERROR_ALERT,
@ -11,6 +10,7 @@ import {
useUpdateUser, useUpdateUser,
} from "@/controllers/API/queries/auth"; } from "@/controllers/API/queries/auth";
import { useGetProfilePicturesQuery } from "@/controllers/API/queries/files"; import { useGetProfilePicturesQuery } from "@/controllers/API/queries/files";
import { ENABLE_PROFILE_ICONS } from "@/customization/feature-flags";
import useAuthStore from "@/stores/authStore"; import useAuthStore from "@/stores/authStore";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
@ -146,7 +146,7 @@ export const GeneralPage = () => {
<GeneralPageHeaderComponent /> <GeneralPageHeaderComponent />
<div className="grid gap-6"> <div className="grid gap-6">
{FeatureFlags.ENABLE_PROFILE_ICONS && ( {ENABLE_PROFILE_ICONS && (
<ProfilePictureFormComponent <ProfilePictureFormComponent
profilePicture={profilePicture} profilePicture={profilePicture}
handleInput={handleInput} handleInput={handleInput}

View file

@ -1,7 +1,8 @@
import { useAddUser } from "@/controllers/API/queries/auth"; import { useAddUser } from "@/controllers/API/queries/auth";
import { CustomLink } from "@/customization/components/custom-link";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import * as Form from "@radix-ui/react-form"; import * as Form from "@radix-ui/react-form";
import { FormEvent, useEffect, useState } from "react"; import { FormEvent, useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import InputComponent from "../../components/inputComponent"; import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
@ -26,7 +27,7 @@ export default function SignUp(): JSX.Element {
const { password, cnfPassword, username } = inputState; const { password, cnfPassword, username } = inputState;
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const navigate = useNavigate(); const navigate = useCustomNavigate();
const { mutate: mutateAddUser } = useAddUser(); const { mutate: mutateAddUser } = useAddUser();
@ -184,11 +185,11 @@ export default function SignUp(): JSX.Element {
</Form.Submit> </Form.Submit>
</div> </div>
<div className="w-full"> <div className="w-full">
<Link to="/login"> <CustomLink to="/login">
<Button className="w-full" variant="outline"> <Button className="w-full" variant="outline">
Already have an account?&nbsp;<b>Sign in</b> Already have an account?&nbsp;<b>Sign in</b>
</Button> </Button>
</Link> </CustomLink>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,6 +1,5 @@
import { uniqueId } from "lodash"; import { uniqueId } from "lodash";
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import CollectionCardComponent from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import PageLayout from "../../components/pageLayout"; import PageLayout from "../../components/pageLayout";
import ShadTooltip from "../../components/shadTooltipComponent"; import ShadTooltip from "../../components/shadTooltipComponent";
@ -9,7 +8,9 @@ import { Button } from "../../components/ui/button";
import StoreCardComponent from "@/components/storeCardComponent"; import StoreCardComponent from "@/components/storeCardComponent";
import { useGetTagsQuery } from "@/controllers/API/queries/store"; import { useGetTagsQuery } from "@/controllers/API/queries/store";
import { Link, useNavigate, useParams } from "react-router-dom"; import { CustomLink } from "@/customization/components/custom-link";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { useParams } from "react-router-dom";
import PaginatorComponent from "../../components/paginatorComponent"; import PaginatorComponent from "../../components/paginatorComponent";
import { TagsSelector } from "../../components/tagsSelectorComponent"; import { TagsSelector } from "../../components/tagsSelectorComponent";
import { Badge } from "../../components/ui/badge"; import { Badge } from "../../components/ui/badge";
@ -62,7 +63,7 @@ export default function StorePage(): JSX.Element {
const [selectFilter, setSelectFilter] = useState("all"); const [selectFilter, setSelectFilter] = useState("all");
const { isFetching, data } = useGetTagsQuery(); const { isFetching, data } = useGetTagsQuery();
const navigate = useNavigate(); const navigate = useCustomNavigate();
useEffect(() => { useEffect(() => {
if (!loadingApiKey) { if (!loadingApiKey) {
@ -289,9 +290,9 @@ export default function StorePage(): JSX.Element {
size="sq" size="sq"
className="gap-2 bg-beta-foreground text-background hover:bg-beta-foreground" className="gap-2 bg-beta-foreground text-background hover:bg-beta-foreground"
> >
<Link to={"/store"} className="cursor-pointer"> <CustomLink to={"/store"} className="cursor-pointer">
<IconComponent name="X" className="h-4 w-4" /> <IconComponent name="X" className="h-4 w-4" />
</Link> </CustomLink>
{id} {id}
</Badge> </Badge>
)} )}

View file

@ -1,8 +1,9 @@
import { useGetRefreshFlows } from "@/controllers/API/queries/flows/use-get-refresh-flows"; import { useGetRefreshFlows } from "@/controllers/API/queries/flows/use-get-refresh-flows";
import { useGetGlobalVariables } from "@/controllers/API/queries/variables"; import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { useTypesStore } from "@/stores/typesStore"; import { useTypesStore } from "@/stores/typesStore";
import { useEffect } from "react"; import { useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import useFlowsManagerStore from "../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore";
import Page from "../FlowPage/components/PageComponent"; import Page from "../FlowPage/components/PageComponent";
@ -10,7 +11,7 @@ export default function ViewPage() {
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const { id } = useParams(); const { id } = useParams();
const navigate = useNavigate(); const navigate = useCustomNavigate();
useGetGlobalVariables(); useGetGlobalVariables();
const flows = useFlowsManagerStore((state) => state.flows); const flows = useFlowsManagerStore((state) => state.flows);

View file

@ -2,7 +2,6 @@ import { lazy } from "react";
import { import {
createBrowserRouter, createBrowserRouter,
createRoutesFromElements, createRoutesFromElements,
Navigate,
Outlet, Outlet,
Route, Route,
} from "react-router-dom"; } from "react-router-dom";
@ -12,6 +11,9 @@ import { ProtectedLoginRoute } from "./components/authLoginGuard";
import { AuthSettingsGuard } from "./components/authSettingsGuard"; import { AuthSettingsGuard } from "./components/authSettingsGuard";
import { StoreGuard } from "./components/storeGuard"; import { StoreGuard } from "./components/storeGuard";
import ContextWrapper from "./contexts"; import ContextWrapper from "./contexts";
import { CustomNavigate } from "./customization/components/custom-navigate";
import { BASENAME } from "./customization/config-constants";
import { ENABLE_CUSTOM_PARAM } from "./customization/feature-flags";
import { AppInitPage } from "./pages/AppInitPage"; import { AppInitPage } from "./pages/AppInitPage";
import { AppWrapperPage } from "./pages/AppWrapperPage"; import { AppWrapperPage } from "./pages/AppWrapperPage";
import { DashboardWrapperPage } from "./pages/DashboardWrapperPage"; import { DashboardWrapperPage } from "./pages/DashboardWrapperPage";
@ -38,7 +40,7 @@ const SignUp = lazy(() => import("./pages/SignUpPage"));
const router = createBrowserRouter( const router = createBrowserRouter(
createRoutesFromElements([ createRoutesFromElements([
<Route <Route
path="/" path={ENABLE_CUSTOM_PARAM ? "/:customParam" : "/"}
element={ element={
<ContextWrapper> <ContextWrapper>
<Outlet /> <Outlet />
@ -57,7 +59,7 @@ const router = createBrowserRouter(
> >
<Route path="" element={<DashboardWrapperPage />}> <Route path="" element={<DashboardWrapperPage />}>
<Route path="" element={<HomePage />}> <Route path="" element={<HomePage />}>
<Route index element={<Navigate replace to={"all"} />} /> <Route index element={<CustomNavigate replace to={"all"} />} />
<Route <Route
path="flows/" path="flows/"
element={<MyCollectionComponent key="flows" type="flow" />} element={<MyCollectionComponent key="flows" type="flow" />}
@ -93,8 +95,11 @@ const router = createBrowserRouter(
/> />
</Route> </Route>
</Route> </Route>
<Route path="/settings" element={<SettingsPage />}> <Route path="settings" element={<SettingsPage />}>
<Route index element={<Navigate replace to={"general"} />} /> <Route
index
element={<CustomNavigate replace to={"general"} />}
/>
<Route <Route
path="global-variables" path="global-variables"
element={<GlobalVariablesPage />} element={<GlobalVariablesPage />}
@ -112,7 +117,7 @@ const router = createBrowserRouter(
<Route path="messages" element={<MessagesPage />} /> <Route path="messages" element={<MessagesPage />} />
</Route> </Route>
<Route <Route
path="/store" path="store"
element={ element={
<StoreGuard> <StoreGuard>
<StorePage /> <StorePage />
@ -120,18 +125,18 @@ const router = createBrowserRouter(
} }
/> />
<Route <Route
path="/store/:id/" path="store/:id/"
element={ element={
<StoreGuard> <StoreGuard>
<StorePage /> <StorePage />
</StoreGuard> </StoreGuard>
} }
/> />
<Route path="/account"> <Route path="account">
<Route path="delete" element={<DeleteAccountPage />}></Route> <Route path="delete" element={<DeleteAccountPage />}></Route>
</Route> </Route>
<Route <Route
path="/admin" path="admin"
element={ element={
<ProtectedAdminRoute> <ProtectedAdminRoute>
<AdminPage /> <AdminPage />
@ -139,19 +144,19 @@ const router = createBrowserRouter(
} }
/> />
</Route> </Route>
<Route path="/flow/:id/"> <Route path="flow/:id/">
<Route path="" element={<DashboardWrapperPage />}> <Route path="" element={<DashboardWrapperPage />}>
<Route path="folder/:folderId/" element={<FlowPage />} /> <Route path="folder/:folderId/" element={<FlowPage />} />
<Route path="" element={<FlowPage />} /> <Route path="" element={<FlowPage />} />
</Route> </Route>
<Route path="view" element={<ViewPage />} /> <Route path="view" element={<ViewPage />} />
</Route> </Route>
<Route path="/playground/:id/"> <Route path="playground/:id/">
<Route path="" element={<PlaygroundPage />} /> <Route path="" element={<PlaygroundPage />} />
</Route> </Route>
</Route> </Route>
<Route <Route
path="/login" path="login"
element={ element={
<ProtectedLoginRoute> <ProtectedLoginRoute>
<LoginPage /> <LoginPage />
@ -159,7 +164,7 @@ const router = createBrowserRouter(
} }
/> />
<Route <Route
path="/signup" path="signup"
element={ element={
<ProtectedLoginRoute> <ProtectedLoginRoute>
<SignUp /> <SignUp />
@ -167,18 +172,19 @@ const router = createBrowserRouter(
} }
/> />
<Route <Route
path="/login/admin" path="login/admin"
element={ element={
<ProtectedLoginRoute> <ProtectedLoginRoute>
<LoginAdminPage /> <LoginAdminPage />
</ProtectedLoginRoute> </ProtectedLoginRoute>
} }
/> />
<Route path="*" element={<Navigate replace to="/" />} />
</Route> </Route>
</Route> </Route>
<Route path="*" element={<CustomNavigate replace to="/" />} />
</Route>, </Route>,
]), ]),
{ basename: BASENAME || undefined },
); );
export default router; export default router;

View file

@ -35,8 +35,6 @@ const useAuthStore = create<AuthStoreType>((set, get) => ({
autoLogin: false, autoLogin: false,
apiKey: null, apiKey: null,
}); });
window.location.href = "/login";
}, },
})); }));

View file

@ -1,17 +1,17 @@
import FeatureFlags from "@/../feature-config.json"; import { ENABLE_LANGFLOW_STORE } from "@/customization/feature-flags";
import { create } from "zustand"; import { create } from "zustand";
import { checkHasApiKey, checkHasStore } from "../controllers/API"; import { checkHasApiKey, checkHasStore } from "../controllers/API";
import { StoreStoreType } from "../types/zustand/store"; import { StoreStoreType } from "../types/zustand/store";
export const useStoreStore = create<StoreStoreType>((set) => ({ export const useStoreStore = create<StoreStoreType>((set) => ({
hasStore: true, hasStore: ENABLE_LANGFLOW_STORE,
validApiKey: false, validApiKey: false,
hasApiKey: false, hasApiKey: false,
loadingApiKey: true, loadingApiKey: true,
checkHasStore: () => { checkHasStore: () => {
checkHasStore().then((res) => { checkHasStore().then((res) => {
set({ set({
hasStore: FeatureFlags.ENABLE_LANGFLOW_STORE && (res?.enabled ?? false), hasStore: ENABLE_LANGFLOW_STORE && (res?.enabled ?? false),
}); });
}); });
}, },

View file

@ -58,6 +58,5 @@
"tests/scheduled-end-to-end/twoEdges.spec.ts", "tests/scheduled-end-to-end/twoEdges.spec.ts",
"tests/scheduled-end-to-end/userSettings.spec.ts", "tests/scheduled-end-to-end/userSettings.spec.ts",
"tests/end-to-end/store.spec.ts", "tests/end-to-end/store.spec.ts",
"tests/end-to-end/logs.spec.ts" "tests/end-to-end/logs.spec.ts" ]
]
} }

View file

@ -4,17 +4,24 @@ import path from "path";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import svgr from "vite-plugin-svgr"; import svgr from "vite-plugin-svgr";
import tsconfigPaths from "vite-tsconfig-paths"; import tsconfigPaths from "vite-tsconfig-paths";
import {
API_ROUTES,
BASENAME,
PORT,
PROXY_TARGET,
} from "./src/customization/config-constants";
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
dotenv.config({ path: path.resolve(__dirname, "../../.env") }); dotenv.config({ path: path.resolve(__dirname, "../../.env") });
const apiRoutes = ["^/api/v1/", "/health"]; const apiRoutes = API_ROUTES || ["^/api/v1/", "/health"];
// Use environment variable to determine the target. // Use environment variable to determine the target.
const target = process.env.VITE_PROXY_TARGET || "http://127.0.0.1:7860"; const target =
process.env.VITE_PROXY_TARGET || PROXY_TARGET || "http://127.0.0.1:7860";
// Use environment variable to determine the UI server port // Use environment variable to determine the UI server port
const port = Number(process.env.VITE_PORT) || 3000; const port = Number(process.env.VITE_PORT) || PORT || 3000;
const proxyTargets = apiRoutes.reduce((proxyObj, route) => { const proxyTargets = apiRoutes.reduce((proxyObj, route) => {
proxyObj[route] = { proxyObj[route] = {
@ -27,6 +34,7 @@ export default defineConfig(({ mode }) => {
}, {}); }, {});
return { return {
basename: (BASENAME || "") + "/",
build: { build: {
outDir: "build", outDir: "build",
}, },