fix: update document title with system features config

This commit is contained in:
NFish 2025-03-14 18:54:17 +08:00
parent 24fce3cc64
commit 00b923651f
23 changed files with 156 additions and 128 deletions

View File

@ -2,7 +2,9 @@
import type { FC } from 'react' import type { FC } from 'react'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useTranslation } from 'react-i18next'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import useDocumentTitle from '@/hooks/use-document-title'
export type IAppDetail = { export type IAppDetail = {
children: React.ReactNode children: React.ReactNode
@ -11,11 +13,13 @@ export type IAppDetail = {
const AppDetail: FC<IAppDetail> = ({ children }) => { const AppDetail: FC<IAppDetail> = ({ children }) => {
const router = useRouter() const router = useRouter()
const { isCurrentWorkspaceDatasetOperator } = useAppContext() const { isCurrentWorkspaceDatasetOperator } = useAppContext()
const { t } = useTranslation()
useDocumentTitle(t('common.menus.appDetail'))
useEffect(() => { useEffect(() => {
if (isCurrentWorkspaceDatasetOperator) if (isCurrentWorkspaceDatasetOperator)
return router.replace('/datasets') return router.replace('/datasets')
}, [isCurrentWorkspaceDatasetOperator]) }, [isCurrentWorkspaceDatasetOperator, router])
return ( return (
<> <>

View File

@ -26,7 +26,6 @@ import { useStore as useTagStore } from '@/app/components/base/tag-management/st
import TagManagementModal from '@/app/components/base/tag-management' import TagManagementModal from '@/app/components/base/tag-management'
import TagFilter from '@/app/components/base/tag-management/filter' import TagFilter from '@/app/components/base/tag-management/filter'
import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label' import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label'
import { useGlobalPublicStore } from '@/context/global-public-context'
const getKey = ( const getKey = (
pageIndex: number, pageIndex: number,
@ -56,7 +55,6 @@ const Apps = () => {
const { t } = useTranslation() const { t } = useTranslation()
const router = useRouter() const router = useRouter()
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext() const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
const { systemFeatures } = useGlobalPublicStore()
const showTagManagementModal = useTagStore(s => s.showTagManagementModal) const showTagManagementModal = useTagStore(s => s.showTagManagementModal)
const [activeTab, setActiveTab] = useTabSearchParams({ const [activeTab, setActiveTab] = useTabSearchParams({
defaultTab: 'all', defaultTab: 'all',
@ -87,16 +85,11 @@ const Apps = () => {
] ]
useEffect(() => { 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') { if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') {
localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY) localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY)
mutate() mutate()
} }
}, [mutate, t, systemFeatures]) }, [mutate, t])
useEffect(() => { useEffect(() => {
if (isCurrentWorkspaceDatasetOperator) if (isCurrentWorkspaceDatasetOperator)

View File

@ -4,17 +4,17 @@ import { RiDiscordFill, RiGithubFill } from '@remixicon/react'
import Link from 'next/link' import Link from 'next/link'
import style from '../list.module.css' import style from '../list.module.css'
import Apps from './Apps' import Apps from './Apps'
import { LicenseStatus } from '@/types/feature'
import { useGlobalPublicStore } from '@/context/global-public-context' import { useGlobalPublicStore } from '@/context/global-public-context'
import useDocumentTitle from '@/hooks/use-document-title'
const AppList = () => { const AppList = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { systemFeatures } = useGlobalPublicStore() const { systemFeatures } = useGlobalPublicStore()
useDocumentTitle(t('common.menus.apps'))
return ( return (
<div className='relative flex flex-col overflow-y-auto bg-background-body shrink-0 h-0 grow'> <div className='relative flex flex-col overflow-y-auto bg-background-body shrink-0 h-0 grow'>
<Apps /> <Apps />
{systemFeatures.license.status === LicenseStatus.NONE && <footer className='px-12 py-6 grow-0 shrink-0'> {!systemFeatures.branding.enabled && <footer className='px-12 py-6 grow-0 shrink-0'>
<h3 className='text-xl font-semibold leading-tight text-gradient'>{t('app.join')}</h3> <h3 className='text-xl font-semibold leading-tight text-gradient'>{t('app.join')}</h3>
<p className='mt-1 system-sm-regular text-text-tertiary'>{t('app.communityIntro')}</p> <p className='mt-1 system-sm-regular text-text-tertiary'>{t('app.communityIntro')}</p>
<div className='flex items-center gap-2 mt-3'> <div className='flex items-center gap-2 mt-3'>

View File

@ -29,9 +29,11 @@ import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
import { useStore as useTagStore } from '@/app/components/base/tag-management/store' import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import { useExternalApiPanel } from '@/context/external-api-panel-context' import { useExternalApiPanel } from '@/context/external-api-panel-context'
import { useGlobalPublicStore } from '@/context/global-public-context'
const Container = () => { const Container = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { systemFeatures } = useGlobalPublicStore()
const router = useRouter() const router = useRouter()
const { currentWorkspace, isCurrentWorkspaceOwner } = useAppContext() const { currentWorkspace, isCurrentWorkspaceOwner } = useAppContext()
const showTagManagementModal = useTagStore(s => s.showTagManagementModal) const showTagManagementModal = useTagStore(s => s.showTagManagementModal)
@ -123,7 +125,7 @@ const Container = () => {
{activeTab === 'dataset' && ( {activeTab === 'dataset' && (
<> <>
<Datasets containerRef={containerRef} tags={tagIDs} keywords={searchKeywords} includeAll={includeAll} /> <Datasets containerRef={containerRef} tags={tagIDs} keywords={searchKeywords} includeAll={includeAll} />
<DatasetFooter /> {!systemFeatures.branding.enabled && <DatasetFooter />}
{showTagManagementModal && ( {showTagManagementModal && (
<TagManagementModal type='knowledge' show={showTagManagementModal} /> <TagManagementModal type='knowledge' show={showTagManagementModal} />
)} )}

View File

@ -3,7 +3,6 @@
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
import useSWRInfinite from 'swr/infinite' import useSWRInfinite from 'swr/infinite'
import { debounce } from 'lodash-es' import { debounce } from 'lodash-es'
import { useTranslation } from 'react-i18next'
import NewDatasetCard from './NewDatasetCard' import NewDatasetCard from './NewDatasetCard'
import DatasetCard from './DatasetCard' import DatasetCard from './DatasetCard'
import type { DataSetListResponse, FetchDatasetsParams } from '@/models/datasets' import type { DataSetListResponse, FetchDatasetsParams } from '@/models/datasets'
@ -57,11 +56,8 @@ const Datasets = ({
const loadingStateRef = useRef(false) const loadingStateRef = useRef(false)
const anchorRef = useRef<HTMLAnchorElement>(null) const anchorRef = useRef<HTMLAnchorElement>(null)
const { t } = useTranslation()
useEffect(() => { useEffect(() => {
loadingStateRef.current = isLoading loadingStateRef.current = isLoading
document.title = `${t('dataset.knowledge')} - Dify`
}, [isLoading]) }, [isLoading])
useEffect(() => { useEffect(() => {
@ -80,7 +76,7 @@ const Datasets = ({
return ( return (
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'> <nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'>
{ isCurrentWorkspaceEditor && <NewDatasetCard ref={anchorRef} /> } {isCurrentWorkspaceEditor && <NewDatasetCard ref={anchorRef} />}
{data?.map(({ data: datasets }) => datasets.map(dataset => ( {data?.map(({ data: datasets }) => datasets.map(dataset => (
<DatasetCard key={dataset.id} dataset={dataset} onSuccess={mutate} />), <DatasetCard key={dataset.id} dataset={dataset} onSuccess={mutate} />),
))} ))}

View File

@ -1,11 +1,12 @@
'use client'
import { useTranslation } from 'react-i18next'
import Container from './Container' import Container from './Container'
import useDocumentTitle from '@/hooks/use-document-title'
const AppList = async () => { const AppList = () => {
const { t } = useTranslation()
useDocumentTitle(t('common.menus.datasets'))
return <Container /> return <Container />
} }
export const metadata = {
title: 'Datasets - Dify',
}
export default AppList export default AppList

View File

@ -1,11 +1,13 @@
import type { FC } from 'react' 'use client'
import type { FC, PropsWithChildren } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next'
import ExploreClient from '@/app/components/explore' import ExploreClient from '@/app/components/explore'
export type IAppDetail = { import useDocumentTitle from '@/hooks/use-document-title'
children: React.ReactNode
}
const AppDetail: FC<IAppDetail> = ({ children }) => { const ExploreLayout: FC<PropsWithChildren> = ({ children }) => {
const { t } = useTranslation()
useDocumentTitle(t('common.menus.explore'))
return ( return (
<ExploreClient> <ExploreClient>
{children} {children}
@ -13,4 +15,4 @@ const AppDetail: FC<IAppDetail> = ({ children }) => {
) )
} }
export default React.memo(AppDetail) export default React.memo(ExploreLayout)

View File

@ -1,22 +1,16 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useTranslation } from 'react-i18next'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import ToolProviderList from '@/app/components/tools/provider-list' import ToolProviderList from '@/app/components/tools/provider-list'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import useDocumentTitle from '@/hooks/use-document-title'
const Layout: FC = () => { const ToolsList: FC = () => {
const { t } = useTranslation()
const router = useRouter() const router = useRouter()
const { isCurrentWorkspaceDatasetOperator } = useAppContext() const { isCurrentWorkspaceDatasetOperator } = useAppContext()
const { t } = useTranslation()
useEffect(() => { useDocumentTitle(t('common.menus.tools'))
if (typeof window !== 'undefined')
document.title = `${t('tools.title')} - Dify`
if (isCurrentWorkspaceDatasetOperator)
return router.replace('/datasets')
}, [isCurrentWorkspaceDatasetOperator, router, t])
useEffect(() => { useEffect(() => {
if (isCurrentWorkspaceDatasetOperator) if (isCurrentWorkspaceDatasetOperator)
@ -25,4 +19,4 @@ const Layout: FC = () => {
return <ToolProviderList /> return <ToolProviderList />
} }
export default React.memo(Layout) export default React.memo(ToolsList)

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import { t } from 'i18next'
import Header from './header' import Header from './header'
import SwrInitor from '@/app/components/swr-initor' import SwrInitor from '@/app/components/swr-initor'
import { AppContextProvider } from '@/context/app-context' import { AppContextProvider } from '@/context/app-context'
@ -34,7 +35,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
} }
export const metadata = { export const metadata = {
title: 'Dify', title: t('common.menus.account'),
} }
export default Layout export default Layout

View File

@ -2,7 +2,6 @@
import type { FC } from 'react' import type { FC } from 'react'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useTranslation } from 'react-i18next'
import ExploreContext from '@/context/explore-context' import ExploreContext from '@/context/explore-context'
import Sidebar from '@/app/components/explore/sidebar' import Sidebar from '@/app/components/explore/sidebar'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
@ -16,7 +15,6 @@ export type IExploreProps = {
const Explore: FC<IExploreProps> = ({ const Explore: FC<IExploreProps> = ({
children, children,
}) => { }) => {
const { t } = useTranslation()
const router = useRouter() const router = useRouter()
const [controlUpdateInstalledApps, setControlUpdateInstalledApps] = useState(0) const [controlUpdateInstalledApps, setControlUpdateInstalledApps] = useState(0)
const { userProfile, isCurrentWorkspaceDatasetOperator } = useAppContext() const { userProfile, isCurrentWorkspaceDatasetOperator } = useAppContext()
@ -24,7 +22,6 @@ const Explore: FC<IExploreProps> = ({
const [installedApps, setInstalledApps] = useState<InstalledApp[]>([]) const [installedApps, setInstalledApps] = useState<InstalledApp[]>([])
useEffect(() => { useEffect(() => {
document.title = `${t('explore.title')} - Dify`;
(async () => { (async () => {
const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} }) const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} })
if (!accounts) if (!accounts)

View File

@ -20,6 +20,7 @@ import { useModalContext } from '@/context/modal-context'
import { LanguagesSupported } from '@/i18n/language' import { LanguagesSupported } from '@/i18n/language'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { Plan } from '@/app/components/billing/type' import { Plan } from '@/app/components/billing/type'
import { useGlobalPublicStore } from '@/context/global-public-context'
export type IAppSelector = { export type IAppSelector = {
isMobile: boolean isMobile: boolean
@ -32,6 +33,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
` `
const router = useRouter() const router = useRouter()
const [aboutVisible, setAboutVisible] = useState(false) const [aboutVisible, setAboutVisible] = useState(false)
const { systemFeatures } = useGlobalPublicStore()
const { locale } = useContext(I18n) const { locale } = useContext(I18n)
const { t } = useTranslation() const { t } = useTranslation()
@ -122,78 +124,80 @@ export default function AppSelector({ isMobile }: IAppSelector) {
<div>{t('common.userProfile.settings')}</div> <div>{t('common.userProfile.settings')}</div>
</div>} </div>}
</Menu.Item> </Menu.Item>
{canEmailSupport && <Menu.Item> {!systemFeatures.branding.enabled && <>
{({ active }) => <a {canEmailSupport && <Menu.Item>
className={classNames(itemClassName, 'group justify-between', {({ active }) => <a
active && 'bg-state-base-hover', className={classNames(itemClassName, 'group justify-between',
)}
href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)}
target='_blank' rel='noopener noreferrer'>
<div>{t('common.userProfile.emailSupport')}</div>
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
</a>}
</Menu.Item>}
<Menu.Item>
{({ active }) => <Link
className={classNames(itemClassName, 'group justify-between',
active && 'bg-state-base-hover',
)}
href='https://github.com/langgenius/dify/discussions/categories/feedbacks'
target='_blank' rel='noopener noreferrer'>
<div>{t('common.userProfile.communityFeedback')}</div>
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
</Link>}
</Menu.Item>
<Menu.Item>
{({ active }) => <Link
className={classNames(itemClassName, 'group justify-between',
active && 'bg-state-base-hover',
)}
href='https://discord.gg/5AEfbxcd9k'
target='_blank' rel='noopener noreferrer'>
<div>{t('common.userProfile.community')}</div>
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
</Link>}
</Menu.Item>
<Menu.Item>
{({ active }) => <Link
className={classNames(itemClassName, 'group justify-between',
active && 'bg-state-base-hover',
)}
href={
locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/' : `https://docs.dify.ai/v/${locale.toLowerCase()}/`
}
target='_blank' rel='noopener noreferrer'>
<div>{t('common.userProfile.helpCenter')}</div>
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
</Link>}
</Menu.Item>
<Menu.Item>
{({ active }) => <Link
className={classNames(itemClassName, 'group justify-between',
active && 'bg-state-base-hover',
)}
href='https://roadmap.dify.ai'
target='_blank' rel='noopener noreferrer'>
<div>{t('common.userProfile.roadmap')}</div>
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
</Link>}
</Menu.Item>
{
document?.body?.getAttribute('data-public-site-about') !== 'hide' && (
<Menu.Item>
{({ active }) => <div className={classNames(itemClassName, 'justify-between',
active && 'bg-state-base-hover', active && 'bg-state-base-hover',
)} onClick={() => setAboutVisible(true)}> )}
<div>{t('common.userProfile.about')}</div> href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)}
<div className='flex items-center'> target='_blank' rel='noopener noreferrer'>
<div className='mr-2 system-xs-regular text-text-tertiary'>{langeniusVersionInfo.current_version}</div> <div>{t('common.userProfile.emailSupport')}</div>
<Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} /> <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
</div> </a>}
</div>} </Menu.Item>}
</Menu.Item> <Menu.Item>
) {({ active }) => <Link
} className={classNames(itemClassName, 'group justify-between',
active && 'bg-state-base-hover',
)}
href='https://github.com/langgenius/dify/discussions/categories/feedbacks'
target='_blank' rel='noopener noreferrer'>
<div>{t('common.userProfile.communityFeedback')}</div>
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
</Link>}
</Menu.Item>
<Menu.Item>
{({ active }) => <Link
className={classNames(itemClassName, 'group justify-between',
active && 'bg-state-base-hover',
)}
href='https://discord.gg/5AEfbxcd9k'
target='_blank' rel='noopener noreferrer'>
<div>{t('common.userProfile.community')}</div>
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
</Link>}
</Menu.Item>
<Menu.Item>
{({ active }) => <Link
className={classNames(itemClassName, 'group justify-between',
active && 'bg-state-base-hover',
)}
href={
locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/' : `https://docs.dify.ai/v/${locale.toLowerCase()}/`
}
target='_blank' rel='noopener noreferrer'>
<div>{t('common.userProfile.helpCenter')}</div>
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
</Link>}
</Menu.Item>
<Menu.Item>
{({ active }) => <Link
className={classNames(itemClassName, 'group justify-between',
active && 'bg-state-base-hover',
)}
href='https://roadmap.dify.ai'
target='_blank' rel='noopener noreferrer'>
<div>{t('common.userProfile.roadmap')}</div>
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
</Link>}
</Menu.Item>
{
document?.body?.getAttribute('data-public-site-about') !== 'hide' && (
<Menu.Item>
{({ active }) => <div className={classNames(itemClassName, 'justify-between',
active && 'bg-state-base-hover',
)} onClick={() => setAboutVisible(true)}>
<div>{t('common.userProfile.about')}</div>
<div className='flex items-center'>
<div className='mr-2 system-xs-regular text-text-tertiary'>{langeniusVersionInfo.current_version}</div>
<Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} />
</div>
</div>}
</Menu.Item>
)
}
</>}
</div> </div>
<Menu.Item> <Menu.Item>
{({ active }) => <div className='p-1' onClick={() => handleLogout()}> {({ active }) => <div className='p-1' onClick={() => handleLogout()}>

View File

@ -17,9 +17,11 @@ import ProviderCard from '@/app/components/tools/provider/card'
import ProviderDetail from '@/app/components/tools/provider/detail' import ProviderDetail from '@/app/components/tools/provider/detail'
import Empty from '@/app/components/tools/add-tool-modal/empty' import Empty from '@/app/components/tools/add-tool-modal/empty'
import { fetchCollectionList } from '@/service/tools' import { fetchCollectionList } from '@/service/tools'
import { useGlobalPublicStore } from '@/context/global-public-context'
const ProviderList = () => { const ProviderList = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { systemFeatures } = useGlobalPublicStore()
const [activeTab, setActiveTab] = useTabSearchParams({ const [activeTab, setActiveTab] = useTabSearchParams({
defaultTab: 'builtin', 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', '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', currentProvider && 'pr-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
)}> )}>
{activeTab === 'builtin' && <ContributeCard />} {activeTab === 'builtin' && !systemFeatures.branding.enabled && <ContributeCard />}
{activeTab === 'api' && <CustomCreateCard onRefreshData={getProviderList} />} {activeTab === 'api' && <CustomCreateCard onRefreshData={getProviderList} />}
{filteredCollectionList.map(collection => ( {filteredCollectionList.map(collection => (
<ProviderCard <ProviderCard

View File

@ -21,8 +21,8 @@ const Contribute: FC = () => {
> >
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'> <div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
<div className='relative shrink-0 flex items-center'> <div className='relative shrink-0 flex items-center'>
<div className='z-10 flex p-3 rounded-[10px] bg-white border-[0.5px] border-primary-100 shadow-md'><RiHammerFill className='w-4 h-4 text-primary-600'/></div> <div className='z-10 flex p-3 rounded-[10px] bg-white border-[0.5px] border-primary-100 shadow-md'><RiHammerFill className='w-4 h-4 text-primary-600' /></div>
<div className='-translate-x-2 flex p-3 rounded-[10px] bg-[#FEF6FB] border-[0.5px] border-[#FCE7F6] shadow-md'><Heart02 className='w-4 h-4 text-[#EE46BC]'/></div> <div className='-translate-x-2 flex p-3 rounded-[10px] bg-[#FEF6FB] border-[0.5px] border-[#FCE7F6] shadow-md'><Heart02 className='w-4 h-4 text-[#EE46BC]' /></div>
</div> </div>
</div> </div>
<div className='mb-3 px-[14px] text-[15px] leading-5 font-semibold'> <div className='mb-3 px-[14px] text-[15px] leading-5 font-semibold'>

View File

@ -7,7 +7,9 @@ import { TanstackQueryIniter } from '@/context/query-client'
import './styles/globals.css' import './styles/globals.css'
import './styles/markdown.scss' import './styles/markdown.scss'
import GlobalPublicStoreProvider from '@/context/global-public-context' 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 = { export const viewport: Viewport = {
width: 'device-width', width: 'device-width',
@ -18,12 +20,19 @@ export const viewport: Viewport = {
} }
export async function generateMetadata(): Promise<Metadata> { export async function generateMetadata(): Promise<Metadata> {
const config = await fetchSystemFeatures() const ret = await fetch(`${API_PREFIX}/system-features`, { cache: 'no-cache' }).then(res => res.json())
if (config.branding.enabled) const config: SystemFeatures = { ...defaultSystemFeatures, ...ret.data }
return { title: config.branding.application_title ?? '-', icons: config.branding.favicon } if (config.branding.enabled) {
return {
title: { template: `%s - ${config.branding.application_title}`, default: config.branding.application_title },
icons: config.branding.favicon,
}
}
return { return {
title: 'Dify', title: {
template: '%s - Dify',
default: 'Dify',
},
} }
} }

View File

@ -1,3 +1,4 @@
'use client'
import Header from './_header' import Header from './_header'
import style from './page.module.css' import style from './page.module.css'

View File

@ -5,7 +5,7 @@ import type { FC, PropsWithChildren } from 'react'
import { useEffect } from 'react' import { useEffect } from 'react'
import type { SystemFeatures } from '@/types/feature' import type { SystemFeatures } from '@/types/feature'
import { defaultSystemFeatures } from '@/types/feature' import { defaultSystemFeatures } from '@/types/feature'
import { fetchSystemFeatures } from '@/service/share' import { getSystemFeatures } from '@/service/common'
type GlobalPublicStore = { type GlobalPublicStore = {
systemFeatures: SystemFeatures systemFeatures: SystemFeatures
@ -22,7 +22,7 @@ const GlobalPublicStoreProvider: FC<PropsWithChildren> = ({
}) => { }) => {
const { data } = useQuery({ const { data } = useQuery({
queryKey: ['systemFeatures'], queryKey: ['systemFeatures'],
queryFn: fetchSystemFeatures, queryFn: getSystemFeatures,
}) })
const { setSystemFeatures } = useGlobalPublicStore() const { setSystemFeatures } = useGlobalPublicStore()
useEffect(() => { useEffect(() => {

View File

@ -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])
}

View File

@ -131,6 +131,7 @@ const translation = {
status: 'beta', status: 'beta',
explore: 'Explore', explore: 'Explore',
apps: 'Studio', apps: 'Studio',
appDetail: 'App Detail',
plugins: 'Plugins', plugins: 'Plugins',
pluginsTips: 'Integrate third-party plugins or create ChatGPT-compatible AI-Plugins.', pluginsTips: 'Integrate third-party plugins or create ChatGPT-compatible AI-Plugins.',
datasets: 'Knowledge', datasets: 'Knowledge',

View File

@ -131,6 +131,7 @@ const translation = {
status: 'ベータ版', status: 'ベータ版',
explore: '探索', explore: '探索',
apps: 'スタジオ', apps: 'スタジオ',
appDetail: 'アプリの詳細',
plugins: 'プラグイン', plugins: 'プラグイン',
pluginsTips: 'サードパーティのプラグインを統合するか、ChatGPT互換のAIプラグインを作成します。', pluginsTips: 'サードパーティのプラグインを統合するか、ChatGPT互換のAIプラグインを作成します。',
datasets: 'ナレッジ', datasets: 'ナレッジ',

View File

@ -131,6 +131,7 @@ const translation = {
status: 'beta', status: 'beta',
explore: '探索', explore: '探索',
apps: '工作室', apps: '工作室',
appDetail: '应用详情',
plugins: '插件', plugins: '插件',
pluginsTips: '集成第三方插件或创建与 ChatGPT 兼容的 AI 插件。', pluginsTips: '集成第三方插件或创建与 ChatGPT 兼容的 AI 插件。',
datasets: '知识库', datasets: '知识库',

View File

@ -127,6 +127,7 @@ const translation = {
status: 'beta', status: 'beta',
explore: '探索', explore: '探索',
apps: '工作室', apps: '工作室',
appDetail: '應用詳情',
plugins: '外掛', plugins: '外掛',
pluginsTips: '整合第三方外掛或建立與 ChatGPT 相容的 AI 外掛。', pluginsTips: '整合第三方外掛或建立與 ChatGPT 相容的 AI 外掛。',
datasets: '知識庫', datasets: '知識庫',

View File

@ -36,6 +36,7 @@ import type {
ModelTypeEnum, ModelTypeEnum,
} from '@/app/components/header/account-setting/model-provider-page/declarations' } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { RETRIEVE_METHOD } from '@/types/app' import type { RETRIEVE_METHOD } from '@/types/app'
import type { SystemFeatures } from '@/types/feature'
type LoginSuccess = { type LoginSuccess = {
result: 'success' result: 'success'
@ -303,6 +304,10 @@ export const fetchSupportRetrievalMethods: Fetcher<RetrievalMethodsRes, string>
return get<RetrievalMethodsRes>(url) return get<RetrievalMethodsRes>(url)
} }
export const getSystemFeatures = () => {
return get<SystemFeatures>('/system-features')
}
export const enableModel = (url: string, body: { model: string; model_type: ModelTypeEnum }) => export const enableModel = (url: string, body: { model: string; model_type: ModelTypeEnum }) =>
patch<CommonResponse>(url, { body }) patch<CommonResponse>(url, { body })

View File

@ -61,6 +61,6 @@ export const defaultSystemFeatures: SystemFeatures = {
login_page_logo: '', login_page_logo: '',
workspace_logo: '', workspace_logo: '',
favicon: '', favicon: '',
application_title: '', application_title: 'test title',
}, },
} }