Browse Source

Perf: remove user profile loading (#22710)

tags/1.7.0
KVOJJJin 3 months ago
parent
commit
a83e4ed9a4
No account linked to committer's email address

+ 3
- 3
web/app/(commonLayout)/layout.tsx View File

import React from 'react' import React from 'react'
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import SwrInitor from '@/app/components/swr-initor'
import SwrInitializer from '@/app/components/swr-initializer'
import { AppContextProvider } from '@/context/app-context' import { AppContextProvider } from '@/context/app-context'
import GA, { GaType } from '@/app/components/base/ga' import GA, { GaType } from '@/app/components/base/ga'
import HeaderWrapper from '@/app/components/header/header-wrapper' import HeaderWrapper from '@/app/components/header/header-wrapper'
return ( return (
<> <>
<GA gaType={GaType.admin} /> <GA gaType={GaType.admin} />
<SwrInitor>
<SwrInitializer>
<AppContextProvider> <AppContextProvider>
<EventEmitterContextProvider> <EventEmitterContextProvider>
<ProviderContextProvider> <ProviderContextProvider>
</ProviderContextProvider> </ProviderContextProvider>
</EventEmitterContextProvider> </EventEmitterContextProvider>
</AppContextProvider> </AppContextProvider>
</SwrInitor>
</SwrInitializer>
</> </>
) )
} }

+ 7
- 2
web/app/account/account-page/index.tsx View File

'use client' 'use client'
import { useState } from 'react' import { useState } from 'react'
import useSWR from 'swr'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
RiGraduationCapFill, RiGraduationCapFill,
import { useGlobalPublicStore } from '@/context/global-public-context' import { useGlobalPublicStore } from '@/context/global-public-context'
import EmailChangeModal from './email-change-modal' import EmailChangeModal from './email-change-modal'
import { validPassword } from '@/config' import { validPassword } from '@/config'
import { fetchAppList } from '@/service/apps'
import type { App } from '@/types/app'


const titleClassName = ` const titleClassName = `
system-sm-semibold text-text-secondary system-sm-semibold text-text-secondary
export default function AccountPage() { export default function AccountPage() {
const { t } = useTranslation() const { t } = useTranslation()
const { systemFeatures } = useGlobalPublicStore() const { systemFeatures } = useGlobalPublicStore()
const { mutateUserProfile, userProfile, apps } = useAppContext()
const { data: appList } = useSWR({ url: '/apps', params: { page: 1, limit: 100, name: '' } }, fetchAppList)
const apps = appList?.data || []
const { mutateUserProfile, userProfile } = useAppContext()
const { isEducationAccount } = useProviderContext() const { isEducationAccount } = useProviderContext()
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const [editNameModalVisible, setEditNameModalVisible] = useState(false) const [editNameModalVisible, setEditNameModalVisible] = useState(false)
{!!apps.length && ( {!!apps.length && (
<Collapse <Collapse
title={`${t('common.account.showAppLength', { length: apps.length })}`} title={`${t('common.account.showAppLength', { length: apps.length })}`}
items={apps.map(app => ({ ...app, key: app.id, name: app.name }))}
items={apps.map((app: App) => ({ ...app, key: app.id, name: app.name }))}
renderItem={renderAppItem} renderItem={renderAppItem}
wrapperClassName='mt-2' wrapperClassName='mt-2'
/> />

+ 1
- 1
web/app/account/layout.tsx View File

import React from 'react' import React from 'react'
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import Header from './header' import Header from './header'
import SwrInitor from '@/app/components/swr-initor'
import SwrInitor from '@/app/components/swr-initializer'
import { AppContextProvider } from '@/context/app-context' import { AppContextProvider } from '@/context/app-context'
import GA, { GaType } from '@/app/components/base/ga' import GA, { GaType } from '@/app/components/base/ga'
import HeaderWrapper from '@/app/components/header/header-wrapper' import HeaderWrapper from '@/app/components/header/header-wrapper'

+ 4
- 12
web/app/components/app-sidebar/app-info.tsx View File

import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useContext, useContextSelector } from 'use-context-selector'
import { useContext } from 'use-context-selector'
import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from 'react'
import { import {
RiDeleteBinLine, RiDeleteBinLine,
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import AppsContext, { useAppContext } from '@/context/app-context'
import { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps' import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps'
import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal' import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal'
const [showImportDSLModal, setShowImportDSLModal] = useState<boolean>(false) const [showImportDSLModal, setShowImportDSLModal] = useState<boolean>(false)
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([]) const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])


const mutateApps = useContextSelector(
AppsContext,
state => state.mutateApps,
)

const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({ const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({
name, name,
icon_type, icon_type,
message: t('app.editDone'), message: t('app.editDone'),
}) })
setAppDetail(app) setAppDetail(app)
mutateApps()
} }
catch { catch {
notify({ type: 'error', message: t('app.editFailed') }) notify({ type: 'error', message: t('app.editFailed') })
} }
}, [appDetail, mutateApps, notify, setAppDetail, t])
}, [appDetail, notify, setAppDetail, t])


const onCopy: DuplicateAppModalProps['onConfirm'] = async ({ name, icon_type, icon, icon_background }) => { const onCopy: DuplicateAppModalProps['onConfirm'] = async ({ name, icon_type, icon, icon_background }) => {
if (!appDetail) if (!appDetail)
message: t('app.newApp.appCreated'), message: t('app.newApp.appCreated'),
}) })
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
mutateApps()
onPlanInfoChanged() onPlanInfoChanged()
getRedirection(true, newApp, replace) getRedirection(true, newApp, replace)
} }
try { try {
await deleteApp(appDetail.id) await deleteApp(appDetail.id)
notify({ type: 'success', message: t('app.appDeleted') }) notify({ type: 'success', message: t('app.appDeleted') })
mutateApps()
onPlanInfoChanged() onPlanInfoChanged()
setAppDetail() setAppDetail()
replace('/apps') replace('/apps')
}) })
} }
setShowConfirmDelete(false) setShowConfirmDelete(false)
}, [appDetail, mutateApps, notify, onPlanInfoChanged, replace, setAppDetail, t])
}, [appDetail, notify, onPlanInfoChanged, replace, setAppDetail, t])


const { isCurrentWorkspaceEditor } = useAppContext() const { isCurrentWorkspaceEditor } = useAppContext()



+ 3
- 5
web/app/components/app/create-app-modal/index.tsx View File

import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'


import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useContext, useContextSelector } from 'use-context-selector'
import { useContext } from 'use-context-selector'
import { RiArrowRightLine, RiArrowRightSLine, RiCommandLine, RiCornerDownLeftLine, RiExchange2Fill } from '@remixicon/react' import { RiArrowRightLine, RiArrowRightSLine, RiCommandLine, RiCornerDownLeftLine, RiExchange2Fill } from '@remixicon/react'
import Link from 'next/link' import Link from 'next/link'
import { useDebounceFn, useKeyPress } from 'ahooks' import { useDebounceFn, useKeyPress } from 'ahooks'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { basePath } from '@/utils/var' import { basePath } from '@/utils/var'
import AppsContext, { useAppContext } from '@/context/app-context'
import { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import type { AppMode } from '@/types/app' import type { AppMode } from '@/types/app'
const { t } = useTranslation() const { t } = useTranslation()
const { push } = useRouter() const { push } = useRouter()
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const mutateApps = useContextSelector(AppsContext, state => state.mutateApps)


const [appMode, setAppMode] = useState<AppMode>('advanced-chat') const [appMode, setAppMode] = useState<AppMode>('advanced-chat')
const [appIcon, setAppIcon] = useState<AppIconSelection>({ type: 'emoji', icon: '🤖', background: '#FFEAD5' }) const [appIcon, setAppIcon] = useState<AppIconSelection>({ type: 'emoji', icon: '🤖', background: '#FFEAD5' })
notify({ type: 'success', message: t('app.newApp.appCreated') }) notify({ type: 'success', message: t('app.newApp.appCreated') })
onSuccess() onSuccess()
onClose() onClose()
mutateApps()
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
getRedirection(isCurrentWorkspaceEditor, app, push) getRedirection(isCurrentWorkspaceEditor, app, push)
} }
notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
} }
isCreatingRef.current = false isCreatingRef.current = false
}, [name, notify, t, appMode, appIcon, description, onSuccess, onClose, mutateApps, push, isCurrentWorkspaceEditor])
}, [name, notify, t, appMode, appIcon, description, onSuccess, onClose, push, isCurrentWorkspaceEditor])


const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 }) const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 })
useKeyPress(['meta.enter', 'ctrl.enter'], () => { useKeyPress(['meta.enter', 'ctrl.enter'], () => {

+ 2
- 2
web/app/components/app/overview/embedded/index.tsx View File

const [option, setOption] = useState<Option>('iframe') const [option, setOption] = useState<Option>('iframe')
const [isCopied, setIsCopied] = useState<OptionStatus>({ iframe: false, scripts: false, chromePlugin: false }) const [isCopied, setIsCopied] = useState<OptionStatus>({ iframe: false, scripts: false, chromePlugin: false })


const { langeniusVersionInfo } = useAppContext()
const { langGeniusVersionInfo } = useAppContext()
const themeBuilder = useThemeContext() const themeBuilder = useThemeContext()
themeBuilder.buildTheme(siteInfo?.chat_color_theme ?? null, siteInfo?.chat_color_theme_inverted ?? false) themeBuilder.buildTheme(siteInfo?.chat_color_theme ?? null, siteInfo?.chat_color_theme_inverted ?? false)
const isTestEnv = langeniusVersionInfo.current_env === 'TESTING' || langeniusVersionInfo.current_env === 'DEVELOPMENT'
const isTestEnv = langGeniusVersionInfo.current_env === 'TESTING' || langGeniusVersionInfo.current_env === 'DEVELOPMENT'
const onClickCopy = () => { const onClickCopy = () => {
if (option === 'chromePlugin') { if (option === 'chromePlugin') {
const splitUrl = OPTION_MAP[option].getContent(appBaseUrl, accessToken).split(': ') const splitUrl = OPTION_MAP[option].getContent(appBaseUrl, accessToken).split(': ')

+ 5
- 16
web/app/components/apps/app-card.tsx View File

'use client' 'use client'


import React, { useCallback, useEffect, useMemo, useState } from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { useContext, useContextSelector } from 'use-context-selector'
import { useContext } from 'use-context-selector'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLine } from '@remixicon/react' import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLine } from '@remixicon/react'
import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps' import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps'
import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal' import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
import AppsContext, { useAppContext } from '@/context/app-context'
import { useAppContext } from '@/context/app-context'
import type { HtmlContentProps } from '@/app/components/base/popover' import type { HtmlContentProps } from '@/app/components/base/popover'
import CustomPopover from '@/app/components/base/popover' import CustomPopover from '@/app/components/base/popover'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
const { onPlanInfoChanged } = useProviderContext() const { onPlanInfoChanged } = useProviderContext()
const { push } = useRouter() const { push } = useRouter()


const mutateApps = useContextSelector(
AppsContext,
state => state.mutateApps,
)

const [showEditModal, setShowEditModal] = useState(false) const [showEditModal, setShowEditModal] = useState(false)
const [showDuplicateModal, setShowDuplicateModal] = useState(false) const [showDuplicateModal, setShowDuplicateModal] = useState(false)
const [showSwitchModal, setShowSwitchModal] = useState<boolean>(false) const [showSwitchModal, setShowSwitchModal] = useState<boolean>(false)
notify({ type: 'success', message: t('app.appDeleted') }) notify({ type: 'success', message: t('app.appDeleted') })
if (onRefresh) if (onRefresh)
onRefresh() onRefresh()
mutateApps()
onPlanInfoChanged() onPlanInfoChanged()
} }
catch (e: any) { catch (e: any) {
}) })
} }
setShowConfirmDelete(false) setShowConfirmDelete(false)
}, [app.id, mutateApps, notify, onPlanInfoChanged, onRefresh, t])
}, [app.id, notify, onPlanInfoChanged, onRefresh, t])


const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({ const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({
name, name,
}) })
if (onRefresh) if (onRefresh)
onRefresh() onRefresh()
mutateApps()
} }
catch { catch {
notify({ type: 'error', message: t('app.editFailed') }) notify({ type: 'error', message: t('app.editFailed') })
} }
}, [app.id, mutateApps, notify, onRefresh, t])
}, [app.id, notify, onRefresh, t])


const onCopy: DuplicateAppModalProps['onConfirm'] = async ({ name, icon_type, icon, icon_background }) => { const onCopy: DuplicateAppModalProps['onConfirm'] = async ({ name, icon_type, icon, icon_background }) => {
try { try {
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
if (onRefresh) if (onRefresh)
onRefresh() onRefresh()
mutateApps()
onPlanInfoChanged() onPlanInfoChanged()
getRedirection(isCurrentWorkspaceEditor, newApp, push) getRedirection(isCurrentWorkspaceEditor, newApp, push)
} }
const onSwitch = () => { const onSwitch = () => {
if (onRefresh) if (onRefresh)
onRefresh() onRefresh()
mutateApps()
setShowSwitchModal(false) setShowSwitchModal(false)
} }


const onUpdateAccessControl = useCallback(() => { const onUpdateAccessControl = useCallback(() => {
if (onRefresh) if (onRefresh)
onRefresh() onRefresh()
mutateApps()
setShowAccessControl(false) setShowAccessControl(false)
}, [onRefresh, mutateApps, setShowAccessControl])
}, [onRefresh, setShowAccessControl])


const Operations = (props: HtmlContentProps) => { const Operations = (props: HtmlContentProps) => {
const { data: userCanAccessApp, isLoading: isGettingUserCanAccessApp } = useGetUserCanAccessApp({ appId: app?.id, enabled: (!!props?.open && systemFeatures.webapp_auth.enabled) }) const { data: userCanAccessApp, isLoading: isGettingUserCanAccessApp } = useGetUserCanAccessApp({ appId: app?.id, enabled: (!!props?.open && systemFeatures.webapp_auth.enabled) })
dateFormat: `${t('datasetDocuments.segment.dateTimeFormat')}`, dateFormat: `${t('datasetDocuments.segment.dateTimeFormat')}`,
}) })
return `${t('datasetDocuments.segment.editedAt')} ${timeText}` return `${t('datasetDocuments.segment.editedAt')} ${timeText}`
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [app.updated_at, app.created_at]) }, [app.updated_at, app.created_at])


return ( return (

+ 1
- 1
web/app/components/base/avatar/index.tsx View File

className={cn(textClassName, 'scale-[0.4] text-center text-white')} className={cn(textClassName, 'scale-[0.4] text-center text-white')}
style={style} style={style}
> >
{name[0].toLocaleUpperCase()}
{name && name[0].toLocaleUpperCase()}
</div> </div>
</div> </div>
) )

+ 2
- 2
web/app/components/billing/apps-full-in-dialog/index.tsx View File

}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { plan } = useProviderContext() const { plan } = useProviderContext()
const { userProfile, langeniusVersionInfo } = useAppContext()
const { userProfile, langGeniusVersionInfo } = useAppContext()
const isTeam = plan.type === Plan.team const isTeam = plan.type === Plan.team
const usage = plan.usage.buildApps const usage = plan.usage.buildApps
const total = plan.total.buildApps const total = plan.total.buildApps
)} )}
{plan.type !== Plan.sandbox && plan.type !== Plan.professional && ( {plan.type !== Plan.sandbox && plan.type !== Plan.professional && (
<Button variant='secondary-accent'> <Button variant='secondary-accent'>
<a target='_blank' rel='noopener noreferrer' href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)}>
<a target='_blank' rel='noopener noreferrer' href={mailToSupport(userProfile.email, plan.type, langGeniusVersionInfo.current_version)}>
{t('billing.apps.contactUs')} {t('billing.apps.contactUs')}
</a> </a>
</Button> </Button>

web/app/components/browser-initor.tsx → web/app/components/browser-initializer.tsx View File

value: sessionStorage, value: sessionStorage,
}) })


const BrowserInitor = ({
const BrowserInitializer = ({
children, children,
}: { children: React.ReactNode }) => {
}: { children: React.ReactElement }) => {
return children return children
} }


export default BrowserInitor
export default BrowserInitializer

+ 7
- 7
web/app/components/header/account-about/index.tsx View File

import { useGlobalPublicStore } from '@/context/global-public-context' import { useGlobalPublicStore } from '@/context/global-public-context'


type IAccountSettingProps = { type IAccountSettingProps = {
langeniusVersionInfo: LangGeniusVersionResponse
langGeniusVersionInfo: LangGeniusVersionResponse
onCancel: () => void onCancel: () => void
} }


export default function AccountAbout({ export default function AccountAbout({
langeniusVersionInfo,
langGeniusVersionInfo,
onCancel, onCancel,
}: IAccountSettingProps) { }: IAccountSettingProps) {
const { t } = useTranslation() const { t } = useTranslation()
const isLatest = langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version
const isLatest = langGeniusVersionInfo.current_version === langGeniusVersionInfo.latest_version
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)


return ( return (
/> />
: <DifyLogo size='large' className='mx-auto' />} : <DifyLogo size='large' className='mx-auto' />}


<div className='text-center text-xs font-normal text-text-tertiary'>Version {langeniusVersionInfo?.current_version}</div>
<div className='text-center text-xs font-normal text-text-tertiary'>Version {langGeniusVersionInfo?.current_version}</div>
<div className='flex flex-col items-center gap-2 text-center text-xs font-normal text-text-secondary'> <div className='flex flex-col items-center gap-2 text-center text-xs font-normal text-text-secondary'>
<div>© {dayjs().year()} LangGenius, Inc., Contributors.</div> <div>© {dayjs().year()} LangGenius, Inc., Contributors.</div>
<div className='text-text-accent'> <div className='text-text-accent'>
<div className='text-xs font-medium text-text-tertiary'> <div className='text-xs font-medium text-text-tertiary'>
{ {
isLatest isLatest
? t('common.about.latestAvailable', { version: langeniusVersionInfo.latest_version })
: t('common.about.nowAvailable', { version: langeniusVersionInfo.latest_version })
? t('common.about.latestAvailable', { version: langGeniusVersionInfo.latest_version })
: t('common.about.nowAvailable', { version: langGeniusVersionInfo.latest_version })
} }
</div> </div>
<div className='flex items-center'> <div className='flex items-center'>
!isLatest && !IS_CE_EDITION && ( !isLatest && !IS_CE_EDITION && (
<Button variant='primary' size='small'> <Button variant='primary' size='small'>
<Link <Link
href={langeniusVersionInfo.release_notes}
href={langGeniusVersionInfo.release_notes}
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
> >
{t('common.about.updateNow')} {t('common.about.updateNow')}

+ 4
- 4
web/app/components/header/account-dropdown/index.tsx View File



const { t } = useTranslation() const { t } = useTranslation()
const docLink = useDocLink() const docLink = useDocLink()
const { userProfile, langeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext()
const { userProfile, langGeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext()
const { isEducationAccount } = useProviderContext() const { isEducationAccount } = useProviderContext()
const { setShowAccountSettingModal } = useModalContext() const { setShowAccountSettingModal } = useModalContext()


<RiInformation2Line className='size-4 shrink-0 text-text-tertiary' /> <RiInformation2Line className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.about')}</div> <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.about')}</div>
<div className='flex shrink-0 items-center'> <div className='flex shrink-0 items-center'>
<div className='system-xs-regular mr-2 text-text-tertiary'>{langeniusVersionInfo.current_version}</div>
<Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} />
<div className='system-xs-regular mr-2 text-text-tertiary'>{langGeniusVersionInfo.current_version}</div>
<Indicator color={langGeniusVersionInfo.current_version === langGeniusVersionInfo.latest_version ? 'green' : 'orange'} />
</div> </div>
</div> </div>
</MenuItem> </MenuItem>
} }
</Menu> </Menu>
{ {
aboutVisible && <AccountAbout onCancel={() => setAboutVisible(false)} langeniusVersionInfo={langeniusVersionInfo} />
aboutVisible && <AccountAbout onCancel={() => setAboutVisible(false)} langGeniusVersionInfo={langGeniusVersionInfo} />
} }
</div > </div >
) )

+ 2
- 2
web/app/components/header/account-dropdown/support.tsx View File

` `
const { t } = useTranslation() const { t } = useTranslation()
const { plan } = useProviderContext() const { plan } = useProviderContext()
const { userProfile, langeniusVersionInfo } = useAppContext()
const { userProfile, langGeniusVersionInfo } = useAppContext()
const canEmailSupport = plan.type === Plan.professional || plan.type === Plan.team || plan.type === Plan.enterprise const canEmailSupport = plan.type === Plan.professional || plan.type === Plan.team || plan.type === Plan.enterprise


return <Menu as="div" className="relative h-full w-full"> return <Menu as="div" className="relative h-full w-full">
className={cn(itemClassName, 'group justify-between', className={cn(itemClassName, 'group justify-between',
'data-[active]:bg-state-base-hover', 'data-[active]:bg-state-base-hover',
)} )}
href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)}
href={mailToSupport(userProfile.email, plan.type, langGeniusVersionInfo.current_version)}
target='_blank' rel='noopener noreferrer'> target='_blank' rel='noopener noreferrer'>
<RiMailSendLine className='size-4 shrink-0 text-text-tertiary' /> <RiMailSendLine className='size-4 shrink-0 text-text-tertiary' />
<div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.emailSupport')}</div> <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.emailSupport')}</div>

+ 5
- 5
web/app/components/header/env-nav/index.tsx View File



const EnvNav = () => { const EnvNav = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { langeniusVersionInfo } = useAppContext()
const showEnvTag = langeniusVersionInfo.current_env === 'TESTING' || langeniusVersionInfo.current_env === 'DEVELOPMENT'
const { langGeniusVersionInfo } = useAppContext()
const showEnvTag = langGeniusVersionInfo.current_env === 'TESTING' || langGeniusVersionInfo.current_env === 'DEVELOPMENT'


if (!showEnvTag) if (!showEnvTag)
return null return null
return ( return (
<div className={` <div className={`
mr-1 flex h-[22px] items-center rounded-md border px-2 text-xs font-medium mr-1 flex h-[22px] items-center rounded-md border px-2 text-xs font-medium
${headerEnvClassName[langeniusVersionInfo.current_env]}
${headerEnvClassName[langGeniusVersionInfo.current_env]}
`}> `}>
{ {
langeniusVersionInfo.current_env === 'TESTING' && (
langGeniusVersionInfo.current_env === 'TESTING' && (
<> <>
<Beaker02 className='h-3 w-3' /> <Beaker02 className='h-3 w-3' />
<div className='ml-1 max-[1280px]:hidden'>{t('common.environment.testing')}</div> <div className='ml-1 max-[1280px]:hidden'>{t('common.environment.testing')}</div>
) )
} }
{ {
langeniusVersionInfo.current_env === 'DEVELOPMENT' && (
langGeniusVersionInfo.current_env === 'DEVELOPMENT' && (
<> <>
<TerminalSquare className='h-3 w-3' /> <TerminalSquare className='h-3 w-3' />
<div className='ml-1 max-[1280px]:hidden'>{t('common.environment.development')}</div> <div className='ml-1 max-[1280px]:hidden'>{t('common.environment.development')}</div>

+ 4
- 5
web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx View File

useEffect(() => { useEffect(() => {
if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier) if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier)
onInstalled() onInstalled()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hasInstalled]) }, [hasInstalled])


const [isInstalling, setIsInstalling] = React.useState(false) const [isInstalling, setIsInstalling] = React.useState(false)
} }
} }


const { langeniusVersionInfo } = useAppContext()
const { langGeniusVersionInfo } = useAppContext()
const isDifyVersionCompatible = useMemo(() => { const isDifyVersionCompatible = useMemo(() => {
if (!langeniusVersionInfo.current_version)
if (!langGeniusVersionInfo.current_version)
return true return true
return gte(langeniusVersionInfo.current_version, payload.meta.minimum_dify_version ?? '0.0.0')
}, [langeniusVersionInfo.current_version, payload.meta.minimum_dify_version])
return gte(langGeniusVersionInfo.current_version, payload.meta.minimum_dify_version ?? '0.0.0')
}, [langGeniusVersionInfo.current_version, payload.meta.minimum_dify_version])


return ( return (
<> <>

+ 4
- 5
web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx View File

useEffect(() => { useEffect(() => {
if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier) if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier)
onInstalled() onInstalled()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hasInstalled]) }, [hasInstalled])


const handleCancel = () => { const handleCancel = () => {
} }
} }


const { langeniusVersionInfo } = useAppContext()
const { langGeniusVersionInfo } = useAppContext()
const { data: pluginDeclaration } = usePluginDeclarationFromMarketPlace(uniqueIdentifier) const { data: pluginDeclaration } = usePluginDeclarationFromMarketPlace(uniqueIdentifier)
const isDifyVersionCompatible = useMemo(() => { const isDifyVersionCompatible = useMemo(() => {
if (!pluginDeclaration || !langeniusVersionInfo.current_version) return true
return gte(langeniusVersionInfo.current_version, pluginDeclaration?.manifest.meta.minimum_dify_version ?? '0.0.0')
}, [langeniusVersionInfo.current_version, pluginDeclaration])
if (!pluginDeclaration || !langGeniusVersionInfo.current_version) return true
return gte(langGeniusVersionInfo.current_version, pluginDeclaration?.manifest.meta.minimum_dify_version ?? '0.0.0')
}, [langGeniusVersionInfo.current_version, pluginDeclaration])


const { canInstall } = useInstallPluginLimit({ ...payload, from: 'marketplace' }) const { canInstall } = useInstallPluginLimit({ ...payload, from: 'marketplace' })
return ( return (

+ 4
- 4
web/app/components/plugins/plugin-item/index.tsx View File

return [PluginSource.github, PluginSource.marketplace].includes(source) ? author : '' return [PluginSource.github, PluginSource.marketplace].includes(source) ? author : ''
}, [source, author]) }, [source, author])


const { langeniusVersionInfo } = useAppContext()
const { langGeniusVersionInfo } = useAppContext()


const isDifyVersionCompatible = useMemo(() => { const isDifyVersionCompatible = useMemo(() => {
if (!langeniusVersionInfo.current_version)
if (!langGeniusVersionInfo.current_version)
return true return true
return gte(langeniusVersionInfo.current_version, declarationMeta.minimum_dify_version ?? '0.0.0')
}, [declarationMeta.minimum_dify_version, langeniusVersionInfo.current_version])
return gte(langGeniusVersionInfo.current_version, declarationMeta.minimum_dify_version ?? '0.0.0')
}, [declarationMeta.minimum_dify_version, langGeniusVersionInfo.current_version])


const handleDelete = () => { const handleDelete = () => {
refreshPluginList({ category } as any) refreshPluginList({ category } as any)

web/app/components/sentry-initor.tsx → web/app/components/sentry-initializer.tsx View File



const isDevelopment = process.env.NODE_ENV === 'development' const isDevelopment = process.env.NODE_ENV === 'development'


const SentryInit = ({
const SentryInitializer = ({
children, children,
}: { children: React.ReactNode }) => {
}: { children: React.ReactElement }) => {
useEffect(() => { useEffect(() => {
const SENTRY_DSN = document?.body?.getAttribute('data-public-sentry-dsn') const SENTRY_DSN = document?.body?.getAttribute('data-public-sentry-dsn')
if (!isDevelopment && SENTRY_DSN) { if (!isDevelopment && SENTRY_DSN) {
return children return children
} }


export default SentryInit
export default SentryInitializer

web/app/components/swr-initor.tsx → web/app/components/swr-initializer.tsx View File

EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION, EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION,
} from '@/app/education-apply/constants' } from '@/app/education-apply/constants'


type SwrInitorProps = {
type SwrInitializerProps = {
children: ReactNode children: ReactNode
} }
const SwrInitor = ({
const SwrInitializer = ({
children, children,
}: SwrInitorProps) => {
}: SwrInitializerProps) => {
const router = useRouter() const router = useRouter()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const consoleToken = decodeURIComponent(searchParams.get('access_token') || '') const consoleToken = decodeURIComponent(searchParams.get('access_token') || '')
: null : null
} }


export default SwrInitor
export default SwrInitializer

+ 9
- 9
web/app/layout.tsx View File

import RoutePrefixHandle from './routePrefixHandle' import RoutePrefixHandle from './routePrefixHandle'
import type { Viewport } from 'next' import type { Viewport } from 'next'
import I18nServer from './components/i18n-server' import I18nServer from './components/i18n-server'
import BrowserInitor from './components/browser-initor'
import SentryInitor from './components/sentry-initor'
import BrowserInitializer from './components/browser-initializer'
import SentryInitializer from './components/sentry-initializer'
import { getLocaleOnServer } from '@/i18n/server' import { getLocaleOnServer } from '@/i18n/server'
import { TanstackQueryIniter } from '@/context/query-client'
import { TanstackQueryInitializer } from '@/context/query-client'
import { ThemeProvider } from 'next-themes' import { ThemeProvider } from 'next-themes'
import './styles/globals.css' import './styles/globals.css'
import './styles/markdown.scss' import './styles/markdown.scss'
className="color-scheme h-full select-auto" className="color-scheme h-full select-auto"
{...datasetMap} {...datasetMap}
> >
<BrowserInitor>
<SentryInitor>
<TanstackQueryIniter>
<BrowserInitializer>
<SentryInitializer>
<TanstackQueryInitializer>
<ThemeProvider <ThemeProvider
attribute='data-theme' attribute='data-theme'
defaultTheme='system' defaultTheme='system'
</GlobalPublicStoreProvider> </GlobalPublicStoreProvider>
</I18nServer> </I18nServer>
</ThemeProvider> </ThemeProvider>
</TanstackQueryIniter>
</SentryInitor>
</BrowserInitor>
</TanstackQueryInitializer>
</SentryInitializer>
</BrowserInitializer>
<RoutePrefixHandle /> <RoutePrefixHandle />
</body> </body>
</html> </html>

+ 21
- 37
web/context/app-context.tsx View File

'use client' 'use client'


import { createRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { createContext, useContext, useContextSelector } from 'use-context-selector' import { createContext, useContext, useContextSelector } from 'use-context-selector'
import type { FC, ReactNode } from 'react' import type { FC, ReactNode } from 'react'
import { fetchAppList } from '@/service/apps'
import Loading from '@/app/components/base/loading'
import { fetchCurrentWorkspace, fetchLanggeniusVersion, fetchUserProfile } from '@/service/common'
import type { App } from '@/types/app'
import { fetchCurrentWorkspace, fetchLangGeniusVersion, fetchUserProfile } from '@/service/common'
import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common' import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
import MaintenanceNotice from '@/app/components/header/maintenance-notice' import MaintenanceNotice from '@/app/components/header/maintenance-notice'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'


export type AppContextValue = { export type AppContextValue = {
apps: App[]
mutateApps: VoidFunction
userProfile: UserProfileResponse userProfile: UserProfileResponse
mutateUserProfile: VoidFunction mutateUserProfile: VoidFunction
currentWorkspace: ICurrentWorkspace currentWorkspace: ICurrentWorkspace
isCurrentWorkspaceEditor: boolean isCurrentWorkspaceEditor: boolean
isCurrentWorkspaceDatasetOperator: boolean isCurrentWorkspaceDatasetOperator: boolean
mutateCurrentWorkspace: VoidFunction mutateCurrentWorkspace: VoidFunction
pageContainerRef: React.RefObject<HTMLDivElement>
langeniusVersionInfo: LangGeniusVersionResponse
langGeniusVersionInfo: LangGeniusVersionResponse
useSelector: typeof useSelector useSelector: typeof useSelector
isLoadingCurrentWorkspace: boolean isLoadingCurrentWorkspace: boolean
} }


const initialLangeniusVersionInfo = {
const userProfilePlaceholder = {
id: '',
name: '',
email: '',
avatar: '',
avatar_url: '',
is_password_set: false,
}

const initialLangGeniusVersionInfo = {
current_env: '', current_env: '',
current_version: '', current_version: '',
latest_version: '', latest_version: '',
} }


const AppContext = createContext<AppContextValue>({ const AppContext = createContext<AppContextValue>({
apps: [],
mutateApps: noop,
userProfile: {
id: '',
name: '',
email: '',
avatar: '',
avatar_url: '',
is_password_set: false,
},
userProfile: userProfilePlaceholder,
currentWorkspace: initialWorkspaceInfo, currentWorkspace: initialWorkspaceInfo,
isCurrentWorkspaceManager: false, isCurrentWorkspaceManager: false,
isCurrentWorkspaceOwner: false, isCurrentWorkspaceOwner: false,
isCurrentWorkspaceDatasetOperator: false, isCurrentWorkspaceDatasetOperator: false,
mutateUserProfile: noop, mutateUserProfile: noop,
mutateCurrentWorkspace: noop, mutateCurrentWorkspace: noop,
pageContainerRef: createRef(),
langeniusVersionInfo: initialLangeniusVersionInfo,
langGeniusVersionInfo: initialLangGeniusVersionInfo,
useSelector, useSelector,
isLoadingCurrentWorkspace: false, isLoadingCurrentWorkspace: false,
}) })
} }


export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) => { export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) => {
const pageContainerRef = useRef<HTMLDivElement>(null)

const { data: appList, mutate: mutateApps } = useSWR({ url: '/apps', params: { page: 1, limit: 30, name: '' } }, fetchAppList)
const { data: userProfileResponse, mutate: mutateUserProfile } = useSWR({ url: '/account/profile', params: {} }, fetchUserProfile) 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: currentWorkspaceResponse, mutate: mutateCurrentWorkspace, isLoading: isLoadingCurrentWorkspace } = useSWR({ url: '/workspaces/current', params: {} }, fetchCurrentWorkspace)


const [userProfile, setUserProfile] = useState<UserProfileResponse>()
const [langeniusVersionInfo, setLangeniusVersionInfo] = useState<LangGeniusVersionResponse>(initialLangeniusVersionInfo)
const [userProfile, setUserProfile] = useState<UserProfileResponse>(userProfilePlaceholder)
const [langGeniusVersionInfo, setLangGeniusVersionInfo] = useState<LangGeniusVersionResponse>(initialLangGeniusVersionInfo)
const [currentWorkspace, setCurrentWorkspace] = useState<ICurrentWorkspace>(initialWorkspaceInfo) const [currentWorkspace, setCurrentWorkspace] = useState<ICurrentWorkspace>(initialWorkspaceInfo)
const isCurrentWorkspaceManager = useMemo(() => ['owner', 'admin'].includes(currentWorkspace.role), [currentWorkspace.role]) const isCurrentWorkspaceManager = useMemo(() => ['owner', 'admin'].includes(currentWorkspace.role), [currentWorkspace.role])
const isCurrentWorkspaceOwner = useMemo(() => currentWorkspace.role === 'owner', [currentWorkspace.role]) const isCurrentWorkspaceOwner = useMemo(() => currentWorkspace.role === 'owner', [currentWorkspace.role])
setUserProfile(result) setUserProfile(result)
const current_version = userProfileResponse.headers.get('x-version') const current_version = userProfileResponse.headers.get('x-version')
const current_env = process.env.NODE_ENV === 'development' ? 'DEVELOPMENT' : userProfileResponse.headers.get('x-env') const current_env = process.env.NODE_ENV === 'development' ? 'DEVELOPMENT' : userProfileResponse.headers.get('x-env')
const versionData = await fetchLanggeniusVersion({ url: '/version', params: { current_version } })
setLangeniusVersionInfo({ ...versionData, current_version, latest_version: versionData.version, current_env })
const versionData = await fetchLangGeniusVersion({ url: '/version', params: { current_version } })
setLangGeniusVersionInfo({ ...versionData, current_version, latest_version: versionData.version, current_env })
} }
}, [userProfileResponse]) }, [userProfileResponse])


setCurrentWorkspace(currentWorkspaceResponse) setCurrentWorkspace(currentWorkspaceResponse)
}, [currentWorkspaceResponse]) }, [currentWorkspaceResponse])


if (!appList || !userProfile)
return <Loading type='app' />

return ( return (
<AppContext.Provider value={{ <AppContext.Provider value={{
apps: appList.data,
mutateApps,
userProfile, userProfile,
mutateUserProfile, mutateUserProfile,
pageContainerRef,
langeniusVersionInfo,
langGeniusVersionInfo,
useSelector, useSelector,
currentWorkspace, currentWorkspace,
isCurrentWorkspaceManager, isCurrentWorkspaceManager,
}}> }}>
<div className='flex h-full flex-col overflow-y-auto'> <div className='flex h-full flex-col overflow-y-auto'>
{globalThis.document?.body?.getAttribute('data-public-maintenance-notice') && <MaintenanceNotice />} {globalThis.document?.body?.getAttribute('data-public-maintenance-notice') && <MaintenanceNotice />}
<div ref={pageContainerRef} className='relative flex grow flex-col overflow-y-auto overflow-x-hidden bg-background-body'>
<div className='relative flex grow flex-col overflow-y-auto overflow-x-hidden bg-background-body'>
{children} {children}
</div> </div>
</div> </div>

+ 1
- 1
web/context/query-client.tsx View File

}, },
}) })


export const TanstackQueryIniter: FC<PropsWithChildren> = (props) => {
export const TanstackQueryInitializer: FC<PropsWithChildren> = (props) => {
const { children } = props const { children } = props
return <QueryClientProvider client={client}> return <QueryClientProvider client={client}>
{children} {children}

+ 1
- 1
web/service/common.ts View File

return get<CommonResponse>(url, params) return get<CommonResponse>(url, params)
} }


export const fetchLanggeniusVersion: Fetcher<LangGeniusVersionResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
export const fetchLangGeniusVersion: Fetcher<LangGeniusVersionResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
return get<LangGeniusVersionResponse>(url, { params }) return get<LangGeniusVersionResponse>(url, { params })
} }



Loading…
Cancel
Save