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', }, }