From 24fce3cc6473ca0402c2708f17589905a8256d77 Mon Sep 17 00:00:00 2001 From: NFish Date: Fri, 14 Mar 2025 15:36:21 +0800 Subject: [PATCH 1/3] chore: use global zustand manage systemFeatures and share between all pages --- .../app/(appDetailLayout)/[appId]/layout.tsx | 14 ++-- .../[appId]/overview/cardView.tsx | 6 +- web/app/(commonLayout)/apps/Apps.tsx | 10 ++- web/app/(commonLayout)/apps/page.tsx | 5 +- web/app/(commonLayout)/layout.tsx | 5 -- web/app/account/account-page/index.tsx | 5 +- .../app/overview/settings/index.tsx | 9 ++- web/app/components/base/logo/logo-site.tsx | 7 +- .../account-setting/members-page/index.tsx | 4 +- web/app/components/header/index.tsx | 6 +- .../components/header/license-env/index.tsx | 3 +- web/app/layout.tsx | 24 ++++-- web/app/signin/layout.tsx | 8 +- web/app/signin/normalForm.tsx | 77 +++++++++---------- web/context/app-context.tsx | 11 +-- web/context/global-public-context.tsx | 34 ++++++++ web/service/common.ts | 17 ++-- web/types/feature.ts | 14 ++++ 18 files changed, 160 insertions(+), 99 deletions(-) create mode 100644 web/context/global-public-context.tsx 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: '', + }, } From 00b923651f0cf71e8232e3862c064487d7c6ea06 Mon Sep 17 00:00:00 2001 From: NFish Date: Fri, 14 Mar 2025 18:54:17 +0800 Subject: [PATCH 2/3] fix: update document title with system features config --- .../app/(appDetailLayout)/layout.tsx | 6 +- web/app/(commonLayout)/apps/Apps.tsx | 9 +- web/app/(commonLayout)/apps/page.tsx | 6 +- web/app/(commonLayout)/datasets/Container.tsx | 4 +- web/app/(commonLayout)/datasets/Datasets.tsx | 6 +- web/app/(commonLayout)/datasets/page.tsx | 11 +- web/app/(commonLayout)/explore/layout.tsx | 14 +- web/app/(commonLayout)/tools/page.tsx | 18 +-- web/app/account/layout.tsx | 3 +- web/app/components/explore/index.tsx | 3 - .../header/account-dropdown/index.tsx | 146 +++++++++--------- web/app/components/tools/provider-list.tsx | 4 +- .../components/tools/provider/contribute.tsx | 4 +- web/app/layout.tsx | 21 ++- web/app/signin/layout.tsx | 1 + web/context/global-public-context.tsx | 4 +- web/hooks/use-document-title.ts | 13 ++ web/i18n/en-US/common.ts | 1 + web/i18n/ja-JP/common.ts | 1 + web/i18n/zh-Hans/common.ts | 1 + web/i18n/zh-Hant/common.ts | 1 + web/service/common.ts | 5 + web/types/feature.ts | 2 +- 23 files changed, 156 insertions(+), 128 deletions(-) create mode 100644 web/hooks/use-document-title.ts diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx index 211b0b3677..126bf45842 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx @@ -2,7 +2,9 @@ import type { FC } from 'react' import React, { useEffect } from 'react' import { useRouter } from 'next/navigation' +import { useTranslation } from 'react-i18next' import { useAppContext } from '@/context/app-context' +import useDocumentTitle from '@/hooks/use-document-title' export type IAppDetail = { children: React.ReactNode @@ -11,11 +13,13 @@ export type IAppDetail = { const AppDetail: FC = ({ children }) => { const router = useRouter() const { isCurrentWorkspaceDatasetOperator } = useAppContext() + const { t } = useTranslation() + useDocumentTitle(t('common.menus.appDetail')) useEffect(() => { if (isCurrentWorkspaceDatasetOperator) return router.replace('/datasets') - }, [isCurrentWorkspaceDatasetOperator]) + }, [isCurrentWorkspaceDatasetOperator, router]) return ( <> diff --git a/web/app/(commonLayout)/apps/Apps.tsx b/web/app/(commonLayout)/apps/Apps.tsx index cba7188b22..b9063d887e 100644 --- a/web/app/(commonLayout)/apps/Apps.tsx +++ b/web/app/(commonLayout)/apps/Apps.tsx @@ -26,7 +26,6 @@ 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, @@ -56,7 +55,6 @@ 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', @@ -87,16 +85,11 @@ const Apps = () => { ] useEffect(() => { - 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, systemFeatures]) + }, [mutate, t]) useEffect(() => { if (isCurrentWorkspaceDatasetOperator) diff --git a/web/app/(commonLayout)/apps/page.tsx b/web/app/(commonLayout)/apps/page.tsx index a9e844f6ee..0fe57e45ec 100644 --- a/web/app/(commonLayout)/apps/page.tsx +++ b/web/app/(commonLayout)/apps/page.tsx @@ -4,17 +4,17 @@ import { RiDiscordFill, RiGithubFill } from '@remixicon/react' import Link from 'next/link' import style from '../list.module.css' import Apps from './Apps' -import { LicenseStatus } from '@/types/feature' import { useGlobalPublicStore } from '@/context/global-public-context' +import useDocumentTitle from '@/hooks/use-document-title' const AppList = () => { const { t } = useTranslation() const { systemFeatures } = useGlobalPublicStore() - + useDocumentTitle(t('common.menus.apps')) return (
- {systemFeatures.license.status === LicenseStatus.NONE &&
+ {!systemFeatures.branding.enabled &&

{t('app.join')}

{t('app.communityIntro')}

diff --git a/web/app/(commonLayout)/datasets/Container.tsx b/web/app/(commonLayout)/datasets/Container.tsx index f484d30a3d..9c5ecbecf9 100644 --- a/web/app/(commonLayout)/datasets/Container.tsx +++ b/web/app/(commonLayout)/datasets/Container.tsx @@ -29,9 +29,11 @@ import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import { useStore as useTagStore } from '@/app/components/base/tag-management/store' import { useAppContext } from '@/context/app-context' import { useExternalApiPanel } from '@/context/external-api-panel-context' +import { useGlobalPublicStore } from '@/context/global-public-context' const Container = () => { const { t } = useTranslation() + const { systemFeatures } = useGlobalPublicStore() const router = useRouter() const { currentWorkspace, isCurrentWorkspaceOwner } = useAppContext() const showTagManagementModal = useTagStore(s => s.showTagManagementModal) @@ -123,7 +125,7 @@ const Container = () => { {activeTab === 'dataset' && ( <> - + {!systemFeatures.branding.enabled && } {showTagManagementModal && ( )} diff --git a/web/app/(commonLayout)/datasets/Datasets.tsx b/web/app/(commonLayout)/datasets/Datasets.tsx index ea918a2b17..f54ddfa259 100644 --- a/web/app/(commonLayout)/datasets/Datasets.tsx +++ b/web/app/(commonLayout)/datasets/Datasets.tsx @@ -3,7 +3,6 @@ import { useEffect, useRef } from 'react' import useSWRInfinite from 'swr/infinite' import { debounce } from 'lodash-es' -import { useTranslation } from 'react-i18next' import NewDatasetCard from './NewDatasetCard' import DatasetCard from './DatasetCard' import type { DataSetListResponse, FetchDatasetsParams } from '@/models/datasets' @@ -57,11 +56,8 @@ const Datasets = ({ const loadingStateRef = useRef(false) const anchorRef = useRef(null) - const { t } = useTranslation() - useEffect(() => { loadingStateRef.current = isLoading - document.title = `${t('dataset.knowledge')} - Dify` }, [isLoading]) useEffect(() => { @@ -80,7 +76,7 @@ const Datasets = ({ return (
} - {canEmailSupport && - {({ active }) => -
{t('common.userProfile.emailSupport')}
- -
} -
} - - {({ active }) => -
{t('common.userProfile.communityFeedback')}
- - } -
- - {({ active }) => -
{t('common.userProfile.community')}
- - } -
- - {({ active }) => -
{t('common.userProfile.helpCenter')}
- - } -
- - {({ active }) => -
{t('common.userProfile.roadmap')}
- - } -
- { - document?.body?.getAttribute('data-public-site-about') !== 'hide' && ( - - {({ active }) => } - - ) - } + )} + href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)} + target='_blank' rel='noopener noreferrer'> +
{t('common.userProfile.emailSupport')}
+ +
} + } + + {({ active }) => +
{t('common.userProfile.communityFeedback')}
+ + } +
+ + {({ active }) => +
{t('common.userProfile.community')}
+ + } +
+ + {({ active }) => +
{t('common.userProfile.helpCenter')}
+ + } +
+ + {({ active }) => +
{t('common.userProfile.roadmap')}
+ + } +
+ { + document?.body?.getAttribute('data-public-site-about') !== 'hide' && ( + + {({ active }) =>
setAboutVisible(true)}> +
{t('common.userProfile.about')}
+
+
{langeniusVersionInfo.current_version}
+ +
+
} +
+ ) + } + }
{({ active }) =>
handleLogout()}> diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index 73c7363641..7a8b97f493 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -17,9 +17,11 @@ import ProviderCard from '@/app/components/tools/provider/card' import ProviderDetail from '@/app/components/tools/provider/detail' import Empty from '@/app/components/tools/add-tool-modal/empty' import { fetchCollectionList } from '@/service/tools' +import { useGlobalPublicStore } from '@/context/global-public-context' const ProviderList = () => { const { t } = useTranslation() + const { systemFeatures } = useGlobalPublicStore() const [activeTab, setActiveTab] = useTabSearchParams({ defaultTab: 'builtin', @@ -98,7 +100,7 @@ const ProviderList = () => { 'relative grid content-start grid-cols-1 gap-4 px-12 pt-2 pb-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0', currentProvider && 'pr-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3', )}> - {activeTab === 'builtin' && } + {activeTab === 'builtin' && !systemFeatures.branding.enabled && } {activeTab === 'api' && } {filteredCollectionList.map(collection => ( { >
-
-
+
+
diff --git a/web/app/layout.tsx b/web/app/layout.tsx index f0bee799b5..de579c8671 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -7,7 +7,9 @@ import { TanstackQueryIniter } from '@/context/query-client' import './styles/globals.css' import './styles/markdown.scss' import GlobalPublicStoreProvider from '@/context/global-public-context' -import { fetchSystemFeatures } from '@/service/share' +import type { SystemFeatures } from '@/types/feature' +import { defaultSystemFeatures } from '@/types/feature' +import { API_PREFIX } from '@/config' export const viewport: Viewport = { width: 'device-width', @@ -18,12 +20,19 @@ export const viewport: Viewport = { } export async function generateMetadata(): Promise { - const config = await fetchSystemFeatures() - if (config.branding.enabled) - return { title: config.branding.application_title ?? '-', icons: config.branding.favicon } - + const ret = await fetch(`${API_PREFIX}/system-features`, { cache: 'no-cache' }).then(res => res.json()) + const config: SystemFeatures = { ...defaultSystemFeatures, ...ret.data } + if (config.branding.enabled) { + return { + title: { template: `%s - ${config.branding.application_title}`, default: config.branding.application_title }, + icons: config.branding.favicon, + } + } return { - title: 'Dify', + title: { + template: '%s - Dify', + default: 'Dify', + }, } } diff --git a/web/app/signin/layout.tsx b/web/app/signin/layout.tsx index ac69ca49c1..d14ab7d8cb 100644 --- a/web/app/signin/layout.tsx +++ b/web/app/signin/layout.tsx @@ -1,3 +1,4 @@ +'use client' import Header from './_header' import style from './page.module.css' diff --git a/web/context/global-public-context.tsx b/web/context/global-public-context.tsx index 462f8ed97b..17a7441642 100644 --- a/web/context/global-public-context.tsx +++ b/web/context/global-public-context.tsx @@ -5,7 +5,7 @@ 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' +import { getSystemFeatures } from '@/service/common' type GlobalPublicStore = { systemFeatures: SystemFeatures @@ -22,7 +22,7 @@ const GlobalPublicStoreProvider: FC = ({ }) => { const { data } = useQuery({ queryKey: ['systemFeatures'], - queryFn: fetchSystemFeatures, + queryFn: getSystemFeatures, }) const { setSystemFeatures } = useGlobalPublicStore() useEffect(() => { diff --git a/web/hooks/use-document-title.ts b/web/hooks/use-document-title.ts new file mode 100644 index 0000000000..e76303be34 --- /dev/null +++ b/web/hooks/use-document-title.ts @@ -0,0 +1,13 @@ +'use client' +import { useLayoutEffect } from 'react' +import { useGlobalPublicStore } from '@/context/global-public-context' + +export default function useDocumentTitle(title: string) { + const { systemFeatures } = useGlobalPublicStore() + useLayoutEffect(() => { + if (systemFeatures.branding.enabled) + document.title = `${title} - ${systemFeatures.branding.application_title}` + else + document.title = `${title} - Dify` + }, [systemFeatures, title]) +} diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index c116e080b4..6dbc3bf6e9 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -131,6 +131,7 @@ const translation = { status: 'beta', explore: 'Explore', apps: 'Studio', + appDetail: 'App Detail', plugins: 'Plugins', pluginsTips: 'Integrate third-party plugins or create ChatGPT-compatible AI-Plugins.', datasets: 'Knowledge', diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index e176f5d139..2215721467 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -131,6 +131,7 @@ const translation = { status: 'ベータ版', explore: '探索', apps: 'スタジオ', + appDetail: 'アプリの詳細', plugins: 'プラグイン', pluginsTips: 'サードパーティのプラグインを統合するか、ChatGPT互換のAIプラグインを作成します。', datasets: 'ナレッジ', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 53c5337cab..9e084b5609 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -131,6 +131,7 @@ const translation = { status: 'beta', explore: '探索', apps: '工作室', + appDetail: '应用详情', plugins: '插件', pluginsTips: '集成第三方插件或创建与 ChatGPT 兼容的 AI 插件。', datasets: '知识库', diff --git a/web/i18n/zh-Hant/common.ts b/web/i18n/zh-Hant/common.ts index 735af54a89..21bd9c925a 100644 --- a/web/i18n/zh-Hant/common.ts +++ b/web/i18n/zh-Hant/common.ts @@ -127,6 +127,7 @@ const translation = { status: 'beta', explore: '探索', apps: '工作室', + appDetail: '應用詳情', plugins: '外掛', pluginsTips: '整合第三方外掛或建立與 ChatGPT 相容的 AI 外掛。', datasets: '知識庫', diff --git a/web/service/common.ts b/web/service/common.ts index fa2536cdc4..7ccc6eb5c3 100644 --- a/web/service/common.ts +++ b/web/service/common.ts @@ -36,6 +36,7 @@ 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' @@ -303,6 +304,10 @@ 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 }) diff --git a/web/types/feature.ts b/web/types/feature.ts index ef20ce75cc..df7a134a04 100644 --- a/web/types/feature.ts +++ b/web/types/feature.ts @@ -61,6 +61,6 @@ export const defaultSystemFeatures: SystemFeatures = { login_page_logo: '', workspace_logo: '', favicon: '', - application_title: '', + application_title: 'test title', }, } From fe237802c97661d34f9ee017e18256ef70a3c6a2 Mon Sep 17 00:00:00 2001 From: NFish Date: Fri, 14 Mar 2025 19:09:51 +0800 Subject: [PATCH 3/3] fix: update Dify text --- web/app/(commonLayout)/datasets/template/template.en.mdx | 2 +- web/app/account/header.tsx | 4 +++- web/app/account/layout.tsx | 6 ------ web/app/account/page.tsx | 5 +++++ web/i18n/en-US/common.ts | 7 ++++--- web/i18n/en-US/explore.ts | 2 +- web/i18n/ja-JP/common.ts | 7 ++++--- web/i18n/ja-JP/explore.ts | 2 +- web/i18n/zh-Hans/common.ts | 7 ++++--- web/i18n/zh-Hans/explore.ts | 2 +- web/i18n/zh-Hant/common.ts | 7 ++++--- web/i18n/zh-Hant/explore.ts | 2 +- 12 files changed, 29 insertions(+), 24 deletions(-) diff --git a/web/app/(commonLayout)/datasets/template/template.en.mdx b/web/app/(commonLayout)/datasets/template/template.en.mdx index ac57e3aef2..a8ed59c04b 100644 --- a/web/app/(commonLayout)/datasets/template/template.en.mdx +++ b/web/app/(commonLayout)/datasets/template/template.en.mdx @@ -6,7 +6,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
### Authentication - Service API of Dify authenticates using an `API-Key`. + Service API authenticates using an `API-Key`. It is suggested that developers store the `API-Key` in the backend instead of sharing or storing it in the client side to avoid the leakage of the `API-Key`, which may lead to property loss. diff --git a/web/app/account/header.tsx b/web/app/account/header.tsx index 694533e5ab..bdda2ec7f6 100644 --- a/web/app/account/header.tsx +++ b/web/app/account/header.tsx @@ -5,9 +5,11 @@ import { useRouter } from 'next/navigation' import Button from '../components/base/button' import Avatar from './avatar' import LogoSite from '@/app/components/base/logo/logo-site' +import { useGlobalPublicStore } from '@/context/global-public-context' const Header = () => { const { t } = useTranslation() + const { systemFeatures } = useGlobalPublicStore() const router = useRouter() const back = () => { @@ -25,7 +27,7 @@ const Header = () => {
diff --git a/web/app/account/layout.tsx b/web/app/account/layout.tsx index d64b72d1af..dd90237447 100644 --- a/web/app/account/layout.tsx +++ b/web/app/account/layout.tsx @@ -1,6 +1,5 @@ import React from 'react' import type { ReactNode } from 'react' -import { t } from 'i18next' import Header from './header' import SwrInitor from '@/app/components/swr-initor' import { AppContextProvider } from '@/context/app-context' @@ -33,9 +32,4 @@ const Layout = ({ children }: { children: ReactNode }) => { ) } - -export const metadata = { - title: t('common.menus.account'), -} - export default Layout diff --git a/web/app/account/page.tsx b/web/app/account/page.tsx index bb7e7f7feb..9ebf0225a0 100644 --- a/web/app/account/page.tsx +++ b/web/app/account/page.tsx @@ -1,6 +1,11 @@ +'use client' +import { useTranslation } from 'react-i18next' import AccountPage from './account-page' +import useDocumentTitle from '@/hooks/use-document-title' export default function Account() { + const { t } = useTranslation() + useDocumentTitle(t('common.menus.account')) return
diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index 6dbc3bf6e9..144b32f74e 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -132,6 +132,7 @@ const translation = { explore: 'Explore', apps: 'Studio', appDetail: 'App Detail', + account: 'Account', plugins: 'Plugins', pluginsTips: 'Integrate third-party plugins or create ChatGPT-compatible AI-Plugins.', datasets: 'Knowledge', @@ -168,7 +169,7 @@ const translation = { account: { account: 'Account', myAccount: 'My Account', - studio: 'Dify Studio', + studio: 'Studio', avatar: 'Avatar', name: 'Name', email: 'Email', @@ -180,8 +181,8 @@ const translation = { newPassword: 'New password', confirmPassword: 'Confirm password', notEqual: 'Two passwords are different.', - langGeniusAccount: 'Dify account', - langGeniusAccountTip: 'Your Dify account and associated user data.', + langGeniusAccount: 'Account\'s data', + langGeniusAccountTip: 'The user data of your account.', editName: 'Edit Name', showAppLength: 'Show {{length}} apps', delete: 'Delete Account', diff --git a/web/i18n/en-US/explore.ts b/web/i18n/en-US/explore.ts index d4368fb171..a8ee22f454 100644 --- a/web/i18n/en-US/explore.ts +++ b/web/i18n/en-US/explore.ts @@ -16,7 +16,7 @@ const translation = { }, }, apps: { - title: 'Explore Apps by Dify', + title: 'Explore Apps', description: 'Use these template apps instantly or customize your own apps based on the templates.', allCategories: 'Recommended', }, diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index 2215721467..0d66228b07 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -132,6 +132,7 @@ const translation = { explore: '探索', apps: 'スタジオ', appDetail: 'アプリの詳細', + account: 'アカウント', plugins: 'プラグイン', pluginsTips: 'サードパーティのプラグインを統合するか、ChatGPT互換のAIプラグインを作成します。', datasets: 'ナレッジ', @@ -177,8 +178,8 @@ const translation = { newPassword: '新しいパスワード', confirmPassword: 'パスワードを確認', notEqual: '2つのパスワードが異なります。', - langGeniusAccount: 'Difyアカウント', - langGeniusAccountTip: 'Difyアカウントと関連するユーザーデータ。', + langGeniusAccount: 'アカウント関連データ', + langGeniusAccountTip: 'アカウントに関連するユーザーデータ。', editName: '名前を編集', showAppLength: '{{length}}アプリを表示', delete: 'アカウントを削除', @@ -186,7 +187,7 @@ const translation = { deleteConfirmTip: '確認のため、登録したメールから次の内容をに送信してください ', account: 'アカウント', myAccount: 'マイアカウント', - studio: 'Difyスタジオ', + studio: 'スタジオ', deletePrivacyLinkTip: 'お客様のデータの取り扱い方法の詳細については、当社の', deletePrivacyLink: 'プライバシーポリシー。', deleteSuccessTip: 'アカウントの削除が完了するまでに時間が必要です。すべて完了しましたら、メールでお知らせします。', diff --git a/web/i18n/ja-JP/explore.ts b/web/i18n/ja-JP/explore.ts index cfc741bc3a..9d06420540 100644 --- a/web/i18n/ja-JP/explore.ts +++ b/web/i18n/ja-JP/explore.ts @@ -16,7 +16,7 @@ const translation = { }, }, apps: { - title: 'Difyによるアプリの探索', + title: 'アプリを探索', description: 'これらのテンプレートアプリを即座に使用するか、テンプレートに基づいて独自のアプリをカスタマイズしてください。', allCategories: '推奨', }, diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 9e084b5609..b4d80736ec 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -132,6 +132,7 @@ const translation = { explore: '探索', apps: '工作室', appDetail: '应用详情', + account: '账户', plugins: '插件', pluginsTips: '集成第三方插件或创建与 ChatGPT 兼容的 AI 插件。', datasets: '知识库', @@ -168,7 +169,7 @@ const translation = { account: { account: '账户', myAccount: '我的账户', - studio: 'Dify 工作室', + studio: '工作室', avatar: '头像', name: '用户名', email: '邮箱', @@ -180,8 +181,8 @@ const translation = { newPassword: '新密码', notEqual: '两个密码不相同', confirmPassword: '确认密码', - langGeniusAccount: 'Dify 账号', - langGeniusAccountTip: '您的 Dify 账号和相关的用户数据。', + langGeniusAccount: '账号关联数据', + langGeniusAccountTip: '您的账号相关的用户数据。', editName: '编辑名字', showAppLength: '显示 {{length}} 个应用', delete: '删除账户', diff --git a/web/i18n/zh-Hans/explore.ts b/web/i18n/zh-Hans/explore.ts index bb7faf0396..05b219b311 100644 --- a/web/i18n/zh-Hans/explore.ts +++ b/web/i18n/zh-Hans/explore.ts @@ -16,7 +16,7 @@ const translation = { }, }, apps: { - title: '探索 Dify 的应用', + title: '探索应用', description: '使用这些模板应用程序,或根据模板自定义您自己的应用程序。', allCategories: '推荐', }, diff --git a/web/i18n/zh-Hant/common.ts b/web/i18n/zh-Hant/common.ts index 21bd9c925a..e36729342c 100644 --- a/web/i18n/zh-Hant/common.ts +++ b/web/i18n/zh-Hant/common.ts @@ -128,6 +128,7 @@ const translation = { explore: '探索', apps: '工作室', appDetail: '應用詳情', + account: '我的帳戶', plugins: '外掛', pluginsTips: '整合第三方外掛或建立與 ChatGPT 相容的 AI 外掛。', datasets: '知識庫', @@ -173,8 +174,8 @@ const translation = { newPassword: '新密碼', notEqual: '兩個密碼不相同', confirmPassword: '確認密碼', - langGeniusAccount: 'Dify 賬號', - langGeniusAccountTip: '您的 Dify 賬號和相關的使用者資料。', + langGeniusAccount: '賬號数据', + langGeniusAccountTip: '您的賬號和相關的使用者資料。', editName: '編輯名字', showAppLength: '顯示 {{length}} 個應用', delete: '刪除帳戶', @@ -182,7 +183,7 @@ const translation = { deleteConfirmTip: '請將以下內容從您的註冊電子郵件發送至 ', account: '帳戶', myAccount: '我的帳戶', - studio: 'Dify 工作室', + studio: '工作室', deletePrivacyLinkTip: '有關我們如何處理您的數據的更多資訊,請參閱我們的', deletePrivacyLink: '隱私策略。', deleteSuccessTip: '您的帳戶需要時間才能完成刪除。完成後,我們會給您發送電子郵件。', diff --git a/web/i18n/zh-Hant/explore.ts b/web/i18n/zh-Hant/explore.ts index 80dfe095dd..3bc90d0d6c 100644 --- a/web/i18n/zh-Hant/explore.ts +++ b/web/i18n/zh-Hant/explore.ts @@ -16,7 +16,7 @@ const translation = { }, }, apps: { - title: '探索 Dify 的應用', + title: '探索應用', description: '使用這些模板應用程式,或根據模板自定義您自己的應用程式。', allCategories: '推薦', },