diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx index 1d96320309..ab8c2b97db 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx @@ -15,17 +15,17 @@ import { } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' -import { useContextSelector } from 'use-context-selector' import s from './style.module.css' import cn from '@/utils/classnames' import { useStore } from '@/app/components/app/store' import AppSideBar from '@/app/components/app-sidebar' import type { NavIcon } from '@/app/components/app-sidebar/navLink' import { fetchAppDetail, fetchAppSSO } from '@/service/apps' -import AppContext, { useAppContext } from '@/context/app-context' +import { useAppContext } from '@/context/app-context' import Loading from '@/app/components/base/loading' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import type { App } from '@/types/app' +import { useGlobalPublicStore } from '@/context/global-public-context' export type IAppDetailLayoutProps = { children: React.ReactNode @@ -56,7 +56,7 @@ const AppDetailLayout: FC = (props) => { icon: NavIcon selectedIcon: NavIcon }>>([]) - const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures) + const { systemFeatures } = useGlobalPublicStore() const getNavigations = useCallback((appId: string, isCurrentWorkspaceEditor: boolean, mode: string) => { const navs = [ @@ -98,7 +98,11 @@ const AppDetailLayout: FC = (props) => { useEffect(() => { if (appDetail) { - document.title = `${(appDetail.name || 'App')} - Dify` + if (systemFeatures.branding.enabled) + document.title = `${(appDetail.name || 'App')} - ${systemFeatures.branding.application_title}` + else + document.title = `${(appDetail.name || 'App')} - Dify` + const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand' const mode = isMobile ? 'collapse' : 'expand' setAppSiderbarExpand(isMobile ? mode : localeMode) @@ -106,7 +110,7 @@ const AppDetailLayout: FC = (props) => { // if ((appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (pathname).endsWith('workflow')) // setAppSiderbarExpand('collapse') } - }, [appDetail, isMobile]) + }, [appDetail, isMobile, pathname, setAppSiderbarExpand, systemFeatures]) useEffect(() => { setAppDetail() diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx index 8f3ee510b8..1285b2205c 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import { useContext, useContextSelector } from 'use-context-selector' +import { useContext } from 'use-context-selector' import AppCard from '@/app/components/app/overview/appCard' import Loading from '@/app/components/base/loading' import { ToastContext } from '@/app/components/base/toast' @@ -20,7 +20,7 @@ import { asyncRunSafe } from '@/utils' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import type { IAppCardProps } from '@/app/components/app/overview/appCard' import { useStore as useAppStore } from '@/app/components/app/store' -import AppContext from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' export type ICardViewProps = { appId: string @@ -31,7 +31,7 @@ const CardView: FC = ({ appId }) => { const { notify } = useContext(ToastContext) const appDetail = useAppStore(state => state.appDetail) const setAppDetail = useAppStore(state => state.setAppDetail) - const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures) + const { systemFeatures } = useGlobalPublicStore() const updateAppDetail = async () => { try { diff --git a/web/app/(commonLayout)/apps/Apps.tsx b/web/app/(commonLayout)/apps/Apps.tsx index 34a28d908e..cba7188b22 100644 --- a/web/app/(commonLayout)/apps/Apps.tsx +++ b/web/app/(commonLayout)/apps/Apps.tsx @@ -26,6 +26,7 @@ import { useStore as useTagStore } from '@/app/components/base/tag-management/st import TagManagementModal from '@/app/components/base/tag-management' import TagFilter from '@/app/components/base/tag-management/filter' import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label' +import { useGlobalPublicStore } from '@/context/global-public-context' const getKey = ( pageIndex: number, @@ -55,6 +56,7 @@ const Apps = () => { const { t } = useTranslation() const router = useRouter() const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext() + const { systemFeatures } = useGlobalPublicStore() const showTagManagementModal = useTagStore(s => s.showTagManagementModal) const [activeTab, setActiveTab] = useTabSearchParams({ defaultTab: 'all', @@ -85,12 +87,16 @@ const Apps = () => { ] useEffect(() => { - document.title = `${t('common.menus.apps')} - Dify` + if (systemFeatures.branding.enabled) + document.title = `${t('common.menus.apps')} - ${systemFeatures.branding.application_title}` + else + document.title = `${t('common.menus.apps')} - Dify` + if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') { localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY) mutate() } - }, [mutate, t]) + }, [mutate, t, systemFeatures]) useEffect(() => { if (isCurrentWorkspaceDatasetOperator) diff --git a/web/app/(commonLayout)/apps/page.tsx b/web/app/(commonLayout)/apps/page.tsx index 972aabc8bc..a9e844f6ee 100644 --- a/web/app/(commonLayout)/apps/page.tsx +++ b/web/app/(commonLayout)/apps/page.tsx @@ -1,16 +1,15 @@ 'use client' -import { useContextSelector } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { RiDiscordFill, RiGithubFill } from '@remixicon/react' import Link from 'next/link' import style from '../list.module.css' import Apps from './Apps' -import AppContext from '@/context/app-context' import { LicenseStatus } from '@/types/feature' +import { useGlobalPublicStore } from '@/context/global-public-context' const AppList = () => { const { t } = useTranslation() - const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures) + const { systemFeatures } = useGlobalPublicStore() return (
diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx index af36d4d961..d07e2a99d9 100644 --- a/web/app/(commonLayout)/layout.tsx +++ b/web/app/(commonLayout)/layout.tsx @@ -30,9 +30,4 @@ const Layout = ({ children }: { children: ReactNode }) => { ) } - -export const metadata = { - title: 'Dify', -} - export default Layout diff --git a/web/app/account/account-page/index.tsx b/web/app/account/account-page/index.tsx index 16d826a7c2..b160f305ef 100644 --- a/web/app/account/account-page/index.tsx +++ b/web/app/account/account-page/index.tsx @@ -16,6 +16,7 @@ import { ToastContext } from '@/app/components/base/toast' import AppIcon from '@/app/components/base/app-icon' import { IS_CE_EDITION } from '@/config' import Input from '@/app/components/base/input' +import { useGlobalPublicStore } from '@/context/global-public-context' const titleClassName = ` system-sm-semibold text-text-secondary @@ -28,7 +29,7 @@ const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/ export default function AccountPage() { const { t } = useTranslation() - const { systemFeatures } = useAppContext() + const { systemFeatures } = useGlobalPublicStore() const { mutateUserProfile, userProfile, apps } = useAppContext() const { notify } = useContext(ToastContext) const [editNameModalVisible, setEditNameModalVisible] = useState(false) @@ -133,7 +134,7 @@ export default function AccountPage() {

{t('common.account.myAccount')}

- +

{userProfile.name}

{userProfile.email}

diff --git a/web/app/components/app/overview/settings/index.tsx b/web/app/components/app/overview/settings/index.tsx index f9d13b9272..1fb054ad75 100644 --- a/web/app/components/app/overview/settings/index.tsx +++ b/web/app/components/app/overview/settings/index.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react' import { RiArrowRightSLine, RiCloseLine } from '@remixicon/react' import Link from 'next/link' import { Trans, useTranslation } from 'react-i18next' -import { useContext, useContextSelector } from 'use-context-selector' +import { useContext } from 'use-context-selector' import { SparklesSoft } from '@/app/components/base/icons/src/public/common' import Modal from '@/app/components/base/modal' import ActionButton from '@/app/components/base/action-button' @@ -21,13 +21,14 @@ import type { AppIconType, AppSSO, Language } from '@/types/app' import { useToastContext } from '@/app/components/base/toast' import { LanguagesSupported, languages } from '@/i18n/language' import Tooltip from '@/app/components/base/tooltip' -import AppContext, { useAppContext } from '@/context/app-context' +import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' import { useModalContext } from '@/context/modal-context' import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import AppIconPicker from '@/app/components/base/app-icon-picker' import I18n from '@/context/i18n' import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' export type ISettingsModalProps = { isChat: boolean @@ -65,7 +66,7 @@ const SettingsModal: FC = ({ onClose, onSave, }) => { - const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures) + const { systemFeatures } = useGlobalPublicStore() const { isCurrentWorkspaceEditor } = useAppContext() const { notify } = useToastContext() const [isShowMore, setIsShowMore] = useState(false) @@ -354,7 +355,7 @@ const SettingsModal: FC = ({
{t(`${prefixSettings}.more.entry`)}

{t(`${prefixSettings}.more.copyRightPlaceholder`)} & {t(`${prefixSettings}.more.privacyPolicyPlaceholder`)}

- +
)} {/* more settings */} diff --git a/web/app/components/base/logo/logo-site.tsx b/web/app/components/base/logo/logo-site.tsx index a399ff3301..301394d46a 100644 --- a/web/app/components/base/logo/logo-site.tsx +++ b/web/app/components/base/logo/logo-site.tsx @@ -2,6 +2,7 @@ import type { FC } from 'react' import classNames from '@/utils/classnames' import { useSelector } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' type LogoSiteProps = { className?: string @@ -10,13 +11,17 @@ type LogoSiteProps = { const LogoSite: FC = ({ className, }) => { + const { systemFeatures } = useGlobalPublicStore() const { theme } = useSelector((s) => { return { theme: s.theme, } }) - const src = theme === 'light' ? '/logo/logo-site.png' : `/logo/logo-site-${theme}.png` + let src = theme === 'light' ? '/logo/logo-site.png' : `/logo/logo-site-${theme}.png` + if (systemFeatures.branding.enabled) + src = systemFeatures.branding.login_page_logo + return ( { @@ -34,7 +35,8 @@ const MembersPage = () => { } const { locale } = useContext(I18n) - const { userProfile, currentWorkspace, isCurrentWorkspaceOwner, isCurrentWorkspaceManager, systemFeatures } = useAppContext() + const { userProfile, currentWorkspace, isCurrentWorkspaceOwner, isCurrentWorkspaceManager } = useAppContext() + const { systemFeatures } = useGlobalPublicStore() const { data, mutate } = useSWR({ url: '/workspaces/current/members' }, fetchMembers) const [inviteModalVisible, setInviteModalVisible] = useState(false) const [invitationResults, setInvitationResults] = useState([]) diff --git a/web/app/components/header/index.tsx b/web/app/components/header/index.tsx index 9a32bbb37a..aa4d8d33c1 100644 --- a/web/app/components/header/index.tsx +++ b/web/app/components/header/index.tsx @@ -4,7 +4,6 @@ import Link from 'next/link' import { useBoolean } from 'ahooks' import { useSelectedLayoutSegment } from 'next/navigation' import { Bars3Icon } from '@heroicons/react/20/solid' -import { useContextSelector } from 'use-context-selector' import HeaderBillingBtn from '../billing/header-billing-btn' import AccountDropdown from './account-dropdown' import AppNav from './app-nav' @@ -15,12 +14,13 @@ import ToolsNav from './tools-nav' import GithubStar from './github-star' import LicenseNav from './license-env' import { WorkspaceProvider } from '@/context/workspace-context' -import AppContext, { useAppContext } from '@/context/app-context' +import { useAppContext } from '@/context/app-context' import LogoSite from '@/app/components/base/logo/logo-site' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { useProviderContext } from '@/context/provider-context' import { useModalContext } from '@/context/modal-context' import { LicenseStatus } from '@/types/feature' +import { useGlobalPublicStore } from '@/context/global-public-context' const navClassName = ` flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl @@ -30,7 +30,7 @@ const navClassName = ` const Header = () => { const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext() - const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures) + const { systemFeatures } = useGlobalPublicStore() const selectedSegment = useSelectedLayoutSegment() const media = useBreakpoints() const isMobile = media === MediaType.mobile diff --git a/web/app/components/header/license-env/index.tsx b/web/app/components/header/license-env/index.tsx index 800d86d2b8..720bf70fd4 100644 --- a/web/app/components/header/license-env/index.tsx +++ b/web/app/components/header/license-env/index.tsx @@ -5,10 +5,11 @@ import { LicenseStatus } from '@/types/feature' import { useTranslation } from 'react-i18next' import { useContextSelector } from 'use-context-selector' import dayjs from 'dayjs' +import { useGlobalPublicStore } from '@/context/global-public-context' const LicenseNav = () => { const { t } = useTranslation() - const systemFeatures = useContextSelector(AppContext, s => s.systemFeatures) + const { systemFeatures } = useGlobalPublicStore() if (systemFeatures.license?.status === LicenseStatus.EXPIRING) { const expiredAt = systemFeatures.license?.expired_at diff --git a/web/app/layout.tsx b/web/app/layout.tsx index da659e6467..f0bee799b5 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -1,4 +1,4 @@ -import type { Viewport } from 'next' +import type { Metadata, Viewport } from 'next' import I18nServer from './components/i18n-server' import BrowserInitor from './components/browser-initor' import SentryInitor from './components/sentry-initor' @@ -6,10 +6,8 @@ import { getLocaleOnServer } from '@/i18n/server' import { TanstackQueryIniter } from '@/context/query-client' import './styles/globals.css' import './styles/markdown.scss' - -export const metadata = { - title: 'Dify', -} +import GlobalPublicStoreProvider from '@/context/global-public-context' +import { fetchSystemFeatures } from '@/service/share' export const viewport: Viewport = { width: 'device-width', @@ -19,6 +17,16 @@ export const viewport: Viewport = { userScalable: false, } +export async function generateMetadata(): Promise { + const config = await fetchSystemFeatures() + if (config.branding.enabled) + return { title: config.branding.application_title ?? '-', icons: config.branding.favicon } + + return { + title: 'Dify', + } +} + const LocaleLayout = ({ children, }: { @@ -50,7 +58,11 @@ const LocaleLayout = ({ - {children} + + + {children} + + diff --git a/web/app/signin/layout.tsx b/web/app/signin/layout.tsx index b404c5c4de..ac69ca49c1 100644 --- a/web/app/signin/layout.tsx +++ b/web/app/signin/layout.tsx @@ -2,8 +2,10 @@ import Header from './_header' import style from './page.module.css' import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' -export default async function SignInLayout({ children }: any) { +export default function SignInLayout({ children }: any) { + const { systemFeatures } = useGlobalPublicStore() return <>
-
+ {systemFeatures.branding.enabled === false &&
© {new Date().getFullYear()} LangGenius, Inc. All rights reserved. -
+
} diff --git a/web/app/signin/normalForm.tsx b/web/app/signin/normalForm.tsx index 1911fa35c6..b60ccb6311 100644 --- a/web/app/signin/normalForm.tsx +++ b/web/app/signin/normalForm.tsx @@ -9,10 +9,11 @@ import MailAndPasswordAuth from './components/mail-and-password-auth' import SocialAuth from './components/social-auth' import SSOAuth from './components/sso-auth' import cn from '@/utils/classnames' -import { getSystemFeatures, invitationCheck } from '@/service/common' -import { LicenseStatus, defaultSystemFeatures } from '@/types/feature' +import { invitationCheck } from '@/service/common' +import { LicenseStatus } from '@/types/feature' import Toast from '@/app/components/base/toast' import { IS_CE_EDITION } from '@/config' +import { useGlobalPublicStore } from '@/context/global-public-context' const NormalForm = () => { const { t } = useTranslation() @@ -23,7 +24,7 @@ const NormalForm = () => { const message = decodeURIComponent(searchParams.get('message') || '') const invite_token = decodeURIComponent(searchParams.get('invite_token') || '') const [isLoading, setIsLoading] = useState(true) - const [systemFeatures, setSystemFeatures] = useState(defaultSystemFeatures) + const { systemFeatures } = useGlobalPublicStore() const [authType, updateAuthType] = useState<'code' | 'password'>('password') const [showORLine, setShowORLine] = useState(false) const [allMethodsAreDisabled, setAllMethodsAreDisabled] = useState(false) @@ -46,12 +47,9 @@ const NormalForm = () => { message, }) } - const features = await getSystemFeatures() - const allFeatures = { ...defaultSystemFeatures, ...features } - setSystemFeatures(allFeatures) - setAllMethodsAreDisabled(!allFeatures.enable_social_oauth_login && !allFeatures.enable_email_code_login && !allFeatures.enable_email_password_login && !allFeatures.sso_enforced_for_signin) - setShowORLine((allFeatures.enable_social_oauth_login || allFeatures.sso_enforced_for_signin) && (allFeatures.enable_email_code_login || allFeatures.enable_email_password_login)) - updateAuthType(allFeatures.enable_email_password_login ? 'password' : 'code') + setAllMethodsAreDisabled(!systemFeatures.enable_social_oauth_login && !systemFeatures.enable_email_code_login && !systemFeatures.enable_email_password_login && !systemFeatures.sso_enforced_for_signin) + setShowORLine((systemFeatures.enable_social_oauth_login || systemFeatures.sso_enforced_for_signin) && (systemFeatures.enable_email_code_login || systemFeatures.enable_email_password_login)) + updateAuthType(systemFeatures.enable_email_password_login ? 'password' : 'code') if (isInviteLink) { const checkRes = await invitationCheck({ url: '/activate/check', @@ -65,10 +63,9 @@ const NormalForm = () => { catch (error) { console.error(error) setAllMethodsAreDisabled(true) - setSystemFeatures(defaultSystemFeatures) } finally { setIsLoading(false) } - }, [consoleToken, refreshToken, message, router, invite_token, isInviteLink]) + }, [consoleToken, refreshToken, message, router, invite_token, isInviteLink, systemFeatures]) useEffect(() => { init() }, [init]) @@ -83,7 +80,7 @@ const NormalForm = () => { } - if (systemFeatures.license?.status === LicenseStatus.LOST) { + if (systemFeatures.license.status === LicenseStatus.LOST) { return
@@ -97,7 +94,7 @@ const NormalForm = () => {
} - if (systemFeatures.license?.status === LicenseStatus.EXPIRED) { + if (systemFeatures.license.status === LicenseStatus.EXPIRED) { return
@@ -111,7 +108,7 @@ const NormalForm = () => {
} - if (systemFeatures.license?.status === LicenseStatus.INACTIVE) { + if (systemFeatures.license.status === LicenseStatus.INACTIVE) { return
@@ -132,11 +129,11 @@ const NormalForm = () => { {isInviteLink ?

{t('login.join')}{workspaceName}

-

{t('login.joinTipStart')}{workspaceName}{t('login.joinTipEnd')}

+ {!systemFeatures.branding.enabled &&

{t('login.joinTipStart')}{workspaceName}{t('login.joinTipEnd')}

}
:

{t('login.pageTitle')}

-

{t('login.welcome')}

+ {!systemFeatures.branding.enabled &&

{t('login.welcome')}

}
}
@@ -184,29 +181,31 @@ const NormalForm = () => {
} -
- {t('login.tosDesc')} -   - {t('login.tos')} -  &  - {t('login.pp')} -
- {IS_CE_EDITION &&
- {t('login.goToInit')} -   - {t('login.setAdminAccount')} -
} + {!systemFeatures.branding.enabled && <> +
+ {t('login.tosDesc')} +   + {t('login.tos')} +  &  + {t('login.pp')} +
+ {IS_CE_EDITION &&
+ {t('login.goToInit')} +   + {t('login.setAdminAccount')} +
} + }
diff --git a/web/context/app-context.tsx b/web/context/app-context.tsx index 7addfb83d4..52067f7891 100644 --- a/web/context/app-context.tsx +++ b/web/context/app-context.tsx @@ -6,19 +6,16 @@ import { createContext, useContext, useContextSelector } from 'use-context-selec import type { FC, ReactNode } from 'react' import { fetchAppList } from '@/service/apps' import Loading from '@/app/components/base/loading' -import { fetchCurrentWorkspace, fetchLanggeniusVersion, fetchUserProfile, getSystemFeatures } from '@/service/common' +import { fetchCurrentWorkspace, fetchLanggeniusVersion, fetchUserProfile } from '@/service/common' import type { App } from '@/types/app' import { Theme } from '@/types/app' import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common' import MaintenanceNotice from '@/app/components/header/maintenance-notice' -import type { SystemFeatures } from '@/types/feature' -import { defaultSystemFeatures } from '@/types/feature' export type AppContextValue = { theme: Theme setTheme: (theme: Theme) => void apps: App[] - systemFeatures: SystemFeatures mutateApps: VoidFunction userProfile: UserProfileResponse mutateUserProfile: VoidFunction @@ -57,7 +54,6 @@ const initialWorkspaceInfo: ICurrentWorkspace = { const AppContext = createContext({ theme: Theme.light, - systemFeatures: defaultSystemFeatures, setTheme: () => { }, apps: [], mutateApps: () => { }, @@ -96,10 +92,6 @@ export const AppContextProvider: FC = ({ children }) => const { data: userProfileResponse, mutate: mutateUserProfile } = useSWR({ url: '/account/profile', params: {} }, fetchUserProfile) const { data: currentWorkspaceResponse, mutate: mutateCurrentWorkspace, isLoading: isLoadingCurrentWorkspace } = useSWR({ url: '/workspaces/current', params: {} }, fetchCurrentWorkspace) - const { data: systemFeatures } = useSWR({ url: '/console/system-features' }, getSystemFeatures, { - fallbackData: defaultSystemFeatures, - }) - const [userProfile, setUserProfile] = useState() const [langeniusVersionInfo, setLangeniusVersionInfo] = useState(initialLangeniusVersionInfo) const [currentWorkspace, setCurrentWorkspace] = useState(initialWorkspaceInfo) @@ -146,7 +138,6 @@ export const AppContextProvider: FC = ({ children }) => theme, setTheme: handleSetTheme, apps: appList.data, - systemFeatures: { ...defaultSystemFeatures, ...systemFeatures }, mutateApps, userProfile, mutateUserProfile, diff --git a/web/context/global-public-context.tsx b/web/context/global-public-context.tsx new file mode 100644 index 0000000000..462f8ed97b --- /dev/null +++ b/web/context/global-public-context.tsx @@ -0,0 +1,34 @@ +'use client' +import { create } from 'zustand' +import { useQuery } from '@tanstack/react-query' +import type { FC, PropsWithChildren } from 'react' +import { useEffect } from 'react' +import type { SystemFeatures } from '@/types/feature' +import { defaultSystemFeatures } from '@/types/feature' +import { fetchSystemFeatures } from '@/service/share' + +type GlobalPublicStore = { + systemFeatures: SystemFeatures + setSystemFeatures: (systemFeatures: SystemFeatures) => void +} + +export const useGlobalPublicStore = create(set => ({ + systemFeatures: defaultSystemFeatures, + setSystemFeatures: (systemFeatures: SystemFeatures) => set(() => ({ systemFeatures })), +})) + +const GlobalPublicStoreProvider: FC = ({ + children, +}) => { + const { data } = useQuery({ + queryKey: ['systemFeatures'], + queryFn: fetchSystemFeatures, + }) + const { setSystemFeatures } = useGlobalPublicStore() + useEffect(() => { + if (data) + setSystemFeatures({ ...defaultSystemFeatures, ...data }) + }, [data, setSystemFeatures]) + return <>{children} +} +export default GlobalPublicStoreProvider diff --git a/web/service/common.ts b/web/service/common.ts index 5910965ec2..fa2536cdc4 100644 --- a/web/service/common.ts +++ b/web/service/common.ts @@ -36,11 +36,10 @@ import type { ModelTypeEnum, } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { RETRIEVE_METHOD } from '@/types/app' -import type { SystemFeatures } from '@/types/feature' type LoginSuccess = { result: 'success' - data: { access_token: string;refresh_token: string } + data: { access_token: string; refresh_token: string } } type LoginFail = { result: 'fail' @@ -304,10 +303,6 @@ export const fetchSupportRetrievalMethods: Fetcher return get(url) } -export const getSystemFeatures = () => { - return get('/system-features') -} - export const enableModel = (url: string, body: { model: string; model_type: ModelTypeEnum }) => patch(url, { body }) @@ -331,20 +326,20 @@ export const uploadRemoteFileInfo = (url: string, isPublic?: boolean) => { export const sendEMailLoginCode = (email: string, language = 'en-US') => post('/email-code-login', { body: { email, language } }) -export const emailLoginWithCode = (data: { email: string;code: string;token: string }) => +export const emailLoginWithCode = (data: { email: string; code: string; token: string }) => post('/email-code-login/validity', { body: data }) export const sendResetPasswordCode = (email: string, language = 'en-US') => - post('/forgot-password', { body: { email, language } }) + post('/forgot-password', { body: { email, language } }) -export const verifyResetPasswordCode = (body: { email: string;code: string;token: string }) => +export const verifyResetPasswordCode = (body: { email: string; code: string; token: string }) => post('/forgot-password/validity', { body }) export const sendDeleteAccountCode = () => get('/account/delete/verify') -export const verifyDeleteAccountCode = (body: { code: string;token: string }) => +export const verifyDeleteAccountCode = (body: { code: string; token: string }) => post('/account/delete', { body }) -export const submitDeleteAccountFeedback = (body: { feedback: string;email: string }) => +export const submitDeleteAccountFeedback = (body: { feedback: string; email: string }) => post('/account/delete/feedback', { body }) diff --git a/web/types/feature.ts b/web/types/feature.ts index 053ce3d7c9..ef20ce75cc 100644 --- a/web/types/feature.ts +++ b/web/types/feature.ts @@ -31,6 +31,13 @@ export type SystemFeatures = { is_allow_register: boolean is_email_setup: boolean license: License + branding: { + enabled: boolean + login_page_logo: string + workspace_logo: string + favicon: string + application_title: string + } } export const defaultSystemFeatures: SystemFeatures = { @@ -49,4 +56,11 @@ export const defaultSystemFeatures: SystemFeatures = { status: LicenseStatus.NONE, expired_at: '', }, + branding: { + enabled: false, + login_page_logo: '', + workspace_logo: '', + favicon: '', + application_title: '', + }, }