fix: check user access permission when visit at explore page

This commit is contained in:
NFish 2025-04-17 16:07:59 +08:00
parent 1126b9d1ec
commit 411a7d346f
13 changed files with 127 additions and 41 deletions

View File

@ -17,7 +17,7 @@ const AppUnavailable: FC<IAppUnavailableProps> = ({
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<div className='flex items-center justify-center w-screen h-screen'> <div className='flex items-center justify-center w-full h-full'>
<h1 className='mr-5 h-[50px] leading-[50px] pr-5 text-[24px] font-medium' <h1 className='mr-5 h-[50px] leading-[50px] pr-5 text-[24px] font-medium'
style={{ style={{
borderRight: '1px solid rgba(0,0,0,.3)', borderRight: '1px solid rgba(0,0,0,.3)',

View File

@ -15,12 +15,16 @@ import type {
AppMeta, AppMeta,
ConversationItem, ConversationItem,
} from '@/models/share' } from '@/models/share'
import { AccessMode } from '@/models/access-control'
export type ChatWithHistoryContextValue = { export type ChatWithHistoryContextValue = {
isFromExplore: boolean
appInfoError?: any appInfoError?: any
appInfoLoading?: boolean appInfoLoading?: boolean
appMeta?: AppMeta appMeta?: AppMeta
appData?: AppData appData?: AppData
accessMode?: AccessMode
userCanAccess?: boolean
appParams?: ChatConfig appParams?: ChatConfig
appChatListDataLoading?: boolean appChatListDataLoading?: boolean
currentConversationId: string currentConversationId: string
@ -52,6 +56,9 @@ export type ChatWithHistoryContextValue = {
} }
export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({
isFromExplore: false,
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
userCanAccess: false,
currentConversationId: '', currentConversationId: '',
appPrevChatTree: [], appPrevChatTree: [],
pinnedConversationList: [], pinnedConversationList: [],
@ -59,21 +66,21 @@ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>
showConfigPanelBeforeChat: false, showConfigPanelBeforeChat: false,
newConversationInputs: {}, newConversationInputs: {},
newConversationInputsRef: { current: {} }, newConversationInputsRef: { current: {} },
handleNewConversationInputsChange: () => {}, handleNewConversationInputsChange: () => { },
inputsForms: [], inputsForms: [],
handleNewConversation: () => {}, handleNewConversation: () => { },
handleStartChat: () => {}, handleStartChat: () => { },
handleChangeConversation: () => {}, handleChangeConversation: () => { },
handlePinConversation: () => {}, handlePinConversation: () => { },
handleUnpinConversation: () => {}, handleUnpinConversation: () => { },
handleDeleteConversation: () => {}, handleDeleteConversation: () => { },
conversationRenaming: false, conversationRenaming: false,
handleRenameConversation: () => {}, handleRenameConversation: () => { },
handleNewConversationCompleted: () => {}, handleNewConversationCompleted: () => { },
chatShouldReloadKey: '', chatShouldReloadKey: '',
isMobile: false, isMobile: false,
isInstalledApp: false, isInstalledApp: false,
handleFeedback: () => {}, handleFeedback: () => { },
currentChatInstanceRef: { current: { handleStop: () => {} } }, currentChatInstanceRef: { current: { handleStop: () => { } } },
}) })
export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext) export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext)

View File

@ -42,6 +42,7 @@ import { changeLanguage } from '@/i18n/i18next-config'
import { useAppFavicon } from '@/hooks/use-app-favicon' import { useAppFavicon } from '@/hooks/use-app-favicon'
import { InputVarType } from '@/app/components/workflow/types' import { InputVarType } from '@/app/components/workflow/types'
import { TransferMethod } from '@/types/app' import { TransferMethod } from '@/types/app'
import { useGetAppAccessMode, useGetUserCanAccessApp } from '@/service/access-control'
function getFormattedChatList(messages: any[]) { function getFormattedChatList(messages: any[]) {
const newChatList: ChatItem[] = [] const newChatList: ChatItem[] = []
@ -72,6 +73,8 @@ function getFormattedChatList(messages: any[]) {
export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo]) const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo])
const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR(installedAppInfo ? null : 'appInfo', fetchAppInfo) const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR(installedAppInfo ? null : 'appInfo', fetchAppInfo)
const { isPending: isGettingAccessMode, data: appAccessMode } = useGetAppAccessMode(installedAppInfo?.app.id || appInfo?.app_id)
const { isPending: isCheckingPermission, data: userCanAccessResult } = useGetUserCanAccessApp(installedAppInfo?.app.id || appInfo?.app_id)
useAppFavicon({ useAppFavicon({
enable: !installedAppInfo, enable: !installedAppInfo,
@ -418,7 +421,9 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
return { return {
appInfoError, appInfoError,
appInfoLoading, appInfoLoading: appInfoLoading || isGettingAccessMode || isCheckingPermission,
accessMode: appAccessMode?.accessMode,
userCanAccess: userCanAccessResult?.result,
isInstalledApp, isInstalledApp,
appId, appId,
currentConversationId, currentConversationId,

View File

@ -27,6 +27,7 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
className, className,
}) => { }) => {
const { const {
userCanAccess,
appInfoError, appInfoError,
appData, appData,
appInfoLoading, appInfoLoading,
@ -57,6 +58,8 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
<Loading type='app' /> <Loading type='app' />
) )
} }
if (!userCanAccess)
return <AppUnavailable code={403} unknownReason='no permission.' />
if (appInfoError) { if (appInfoError) {
return ( return (
@ -102,10 +105,12 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
export type ChatWithHistoryWrapProps = { export type ChatWithHistoryWrapProps = {
installedAppInfo?: InstalledApp installedAppInfo?: InstalledApp
className?: string className?: string
isFromExplore?: boolean
} }
const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
installedAppInfo, installedAppInfo,
className, className,
isFromExplore,
}) => { }) => {
const media = useBreakpoints() const media = useBreakpoints()
const isMobile = media === MediaType.mobile const isMobile = media === MediaType.mobile
@ -114,6 +119,8 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
const { const {
appInfoError, appInfoError,
appInfoLoading, appInfoLoading,
accessMode,
userCanAccess,
appData, appData,
appParams, appParams,
appMeta, appMeta,
@ -146,9 +153,12 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
return ( return (
<ChatWithHistoryContext.Provider value={{ <ChatWithHistoryContext.Provider value={{
isFromExplore: !!isFromExplore,
appInfoError, appInfoError,
appInfoLoading, appInfoLoading,
appData, appData,
accessMode,
userCanAccess,
appParams, appParams,
appMeta, appMeta,
appChatListDataLoading, appChatListDataLoading,
@ -187,6 +197,7 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({ const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
installedAppInfo, installedAppInfo,
className, className,
isFromExplore = false,
}) => { }) => {
const [initialized, setInitialized] = useState(false) const [initialized, setInitialized] = useState(false)
const [appUnavailable, setAppUnavailable] = useState<boolean>(false) const [appUnavailable, setAppUnavailable] = useState<boolean>(false)
@ -222,6 +233,7 @@ const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
<ChatWithHistoryWrap <ChatWithHistoryWrap
installedAppInfo={installedAppInfo} installedAppInfo={installedAppInfo}
className={className} className={className}
isFromExplore={isFromExplore}
/> />
) )
} }

View File

@ -12,10 +12,13 @@ import type { ConversationItem } from '@/models/share'
import Confirm from '@/app/components/base/confirm' import Confirm from '@/app/components/base/confirm'
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal' import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
import MenuDropdown from '@/app/components/share/text-generation/menu-dropdown' import MenuDropdown from '@/app/components/share/text-generation/menu-dropdown'
import { AccessMode } from '@/models/access-control'
const Sidebar = () => { const Sidebar = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { const {
isFromExplore,
accessMode,
appData, appData,
pinnedConversationList, pinnedConversationList,
conversationList, conversationList,
@ -117,7 +120,7 @@ const Sidebar = () => {
} }
</div> </div>
<div className='flex items-center justify-between px-4 pb-4 '> <div className='flex items-center justify-between px-4 pb-4 '>
<MenuDropdown placement='top-start' data={appData?.site} /> <MenuDropdown hideLogout={isFromExplore || accessMode === AccessMode.PUBLIC} placement='top-start' data={appData?.site} />
{appData?.site.copyright && ( {appData?.site.copyright && (
<div className='text-xs text-gray-400 truncate'> <div className='text-xs text-gray-400 truncate'>
© {(new Date()).getFullYear()} {appData?.site.copyright} © {(new Date()).getFullYear()} {appData?.site.copyright}

View File

@ -14,8 +14,12 @@ import type {
AppMeta, AppMeta,
ConversationItem, ConversationItem,
} from '@/models/share' } from '@/models/share'
import { AccessMode } from '@/models/access-control'
export type EmbeddedChatbotContextValue = { export type EmbeddedChatbotContextValue = {
isFromExplore: boolean
accessMode?: AccessMode
userCanAccess?: boolean
appInfoError?: any appInfoError?: any
appInfoLoading?: boolean appInfoLoading?: boolean
appMeta?: AppMeta appMeta?: AppMeta
@ -46,6 +50,9 @@ export type EmbeddedChatbotContextValue = {
} }
export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({
isFromExplore: false,
userCanAccess: false,
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
currentConversationId: '', currentConversationId: '',
appPrevChatList: [], appPrevChatList: [],
pinnedConversationList: [], pinnedConversationList: [],
@ -53,16 +60,16 @@ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>
showConfigPanelBeforeChat: false, showConfigPanelBeforeChat: false,
newConversationInputs: {}, newConversationInputs: {},
newConversationInputsRef: { current: {} }, newConversationInputsRef: { current: {} },
handleNewConversationInputsChange: () => {}, handleNewConversationInputsChange: () => { },
inputsForms: [], inputsForms: [],
handleNewConversation: () => {}, handleNewConversation: () => { },
handleStartChat: () => {}, handleStartChat: () => { },
handleChangeConversation: () => {}, handleChangeConversation: () => { },
handleNewConversationCompleted: () => {}, handleNewConversationCompleted: () => { },
chatShouldReloadKey: '', chatShouldReloadKey: '',
isMobile: false, isMobile: false,
isInstalledApp: false, isInstalledApp: false,
handleFeedback: () => {}, handleFeedback: () => { },
currentChatInstanceRef: { current: { handleStop: () => {} } }, currentChatInstanceRef: { current: { handleStop: () => { } } },
}) })
export const useEmbeddedChatbotContext = () => useContext(EmbeddedChatbotContext) export const useEmbeddedChatbotContext = () => useContext(EmbeddedChatbotContext)

View File

@ -35,6 +35,7 @@ import { changeLanguage } from '@/i18n/i18next-config'
import { InputVarType } from '@/app/components/workflow/types' import { InputVarType } from '@/app/components/workflow/types'
import { TransferMethod } from '@/types/app' import { TransferMethod } from '@/types/app'
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils' import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
import { useGetAppAccessMode, useGetUserCanAccessApp } from '@/service/access-control'
function getFormattedChatList(messages: any[]) { function getFormattedChatList(messages: any[]) {
const newChatList: ChatItem[] = [] const newChatList: ChatItem[] = []
@ -65,6 +66,8 @@ function getFormattedChatList(messages: any[]) {
export const useEmbeddedChatbot = () => { export const useEmbeddedChatbot = () => {
const isInstalledApp = false const isInstalledApp = false
const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR('appInfo', fetchAppInfo) const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR('appInfo', fetchAppInfo)
const { isPending: isGettingAccessMode, data: appAccessMode } = useGetAppAccessMode(appInfo?.app_id)
const { isPending: isCheckingPermission, data: userCanAccessResult } = useGetUserCanAccessApp(appInfo?.app_id)
const appData = useMemo(() => { const appData = useMemo(() => {
return appInfo return appInfo
@ -319,7 +322,9 @@ export const useEmbeddedChatbot = () => {
return { return {
appInfoError, appInfoError,
appInfoLoading, appInfoLoading: appInfoLoading || isGettingAccessMode || isCheckingPermission,
accessMode: appAccessMode?.accessMode,
userCanAccess: userCanAccessResult?.result,
isInstalledApp, isInstalledApp,
appId, appId,
currentConversationId, currentConversationId,

View File

@ -26,6 +26,8 @@ import Tooltip from '@/app/components/base/tooltip'
const Chatbot = () => { const Chatbot = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { const {
isFromExplore,
userCanAccess,
isMobile, isMobile,
appInfoError, appInfoError,
appInfoLoading, appInfoLoading,
@ -59,6 +61,9 @@ const Chatbot = () => {
) )
} }
if (!userCanAccess)
return <AppUnavailable code={403} unknownReason='no permission.' />
if (appInfoError) { if (appInfoError) {
return ( return (
<AppUnavailable /> <AppUnavailable />
@ -91,7 +96,7 @@ const Chatbot = () => {
popupContent={t('share.chat.resetChat')} popupContent={t('share.chat.resetChat')}
> >
<div className='p-1.5 bg-white border-[0.5px] border-gray-100 rounded-lg shadow-md cursor-pointer' onClick={handleNewConversation}> <div className='p-1.5 bg-white border-[0.5px] border-gray-100 rounded-lg shadow-md cursor-pointer' onClick={handleNewConversation}>
<RiLoopLeftLine className="h-4 w-4 text-gray-500"/> <RiLoopLeftLine className="h-4 w-4 text-gray-500" />
</div> </div>
</Tooltip> </Tooltip>
</div> </div>
@ -105,7 +110,11 @@ const Chatbot = () => {
) )
} }
const EmbeddedChatbotWrapper = () => { type EmbeddedChatbotProps = {
isFromExplore?: boolean
}
const EmbeddedChatbotWrapper = ({ isFromExplore }: EmbeddedChatbotProps) => {
const media = useBreakpoints() const media = useBreakpoints()
const isMobile = media === MediaType.mobile const isMobile = media === MediaType.mobile
const themeBuilder = useThemeContext() const themeBuilder = useThemeContext()
@ -114,6 +123,8 @@ const EmbeddedChatbotWrapper = () => {
appInfoError, appInfoError,
appInfoLoading, appInfoLoading,
appData, appData,
accessMode,
userCanAccess,
appParams, appParams,
appMeta, appMeta,
appChatListDataLoading, appChatListDataLoading,
@ -139,6 +150,9 @@ const EmbeddedChatbotWrapper = () => {
} = useEmbeddedChatbot() } = useEmbeddedChatbot()
return <EmbeddedChatbotContext.Provider value={{ return <EmbeddedChatbotContext.Provider value={{
isFromExplore: !!isFromExplore,
userCanAccess,
accessMode,
appInfoError, appInfoError,
appInfoLoading, appInfoLoading,
appData, appData,
@ -171,7 +185,7 @@ const EmbeddedChatbotWrapper = () => {
</EmbeddedChatbotContext.Provider> </EmbeddedChatbotContext.Provider>
} }
const EmbeddedChatbot = () => { const EmbeddedChatbot = ({ isFromExplore = false }: EmbeddedChatbotProps) => {
const [initialized, setInitialized] = useState(false) const [initialized, setInitialized] = useState(false)
const [appUnavailable, setAppUnavailable] = useState<boolean>(false) const [appUnavailable, setAppUnavailable] = useState<boolean>(false)
const [isUnknownReason, setIsUnknownReason] = useState<boolean>(false) const [isUnknownReason, setIsUnknownReason] = useState<boolean>(false)
@ -200,7 +214,7 @@ const EmbeddedChatbot = () => {
if (appUnavailable) if (appUnavailable)
return <AppUnavailable isUnknownReason={isUnknownReason} /> return <AppUnavailable isUnknownReason={isUnknownReason} />
return <EmbeddedChatbotWrapper /> return <EmbeddedChatbotWrapper isFromExplore={isFromExplore} />
} }
export default EmbeddedChatbot export default EmbeddedChatbot

View File

@ -26,15 +26,15 @@ const InstalledApp: FC<IInstalledAppProps> = ({
} }
return ( return (
<div className='h-full py-2 pl-0 pr-2 sm:p-2'> <div className='h-full py-2 pl-0 pr-2 sm:p-2 bg-background-default'>
{installedApp.app.mode !== 'completion' && installedApp.app.mode !== 'workflow' && ( {installedApp.app.mode !== 'completion' && installedApp.app.mode !== 'workflow' && (
<ChatWithHistory installedAppInfo={installedApp} className='rounded-2xl shadow-md overflow-hidden' /> <ChatWithHistory isFromExplore installedAppInfo={installedApp} className='rounded-2xl shadow-md overflow-hidden' />
)} )}
{installedApp.app.mode === 'completion' && ( {installedApp.app.mode === 'completion' && (
<TextGenerationApp isInstalledApp installedAppInfo={installedApp}/> <TextGenerationApp isFromExplore isInstalledApp installedAppInfo={installedApp} />
)} )}
{installedApp.app.mode === 'workflow' && ( {installedApp.app.mode === 'workflow' && (
<TextGenerationApp isWorkflow isInstalledApp installedAppInfo={installedApp}/> <TextGenerationApp isFromExplore isWorkflow isInstalledApp installedAppInfo={installedApp} />
)} )}
</div> </div>
) )

View File

@ -11,6 +11,7 @@ import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import TabHeader from '../../base/tab-header' import TabHeader from '../../base/tab-header'
import Button from '../../base/button' import Button from '../../base/button'
import { checkOrSetAccessToken } from '../utils' import { checkOrSetAccessToken } from '../utils'
import AppUnavailable from '../../base/app-unavailable'
import s from './style.module.css' import s from './style.module.css'
import RunBatch from './run-batch' import RunBatch from './run-batch'
import ResDownload from './run-batch/res-download' import ResDownload from './run-batch/res-download'
@ -38,6 +39,8 @@ import Toast from '@/app/components/base/toast'
import type { VisionFile, VisionSettings } from '@/types/app' import type { VisionFile, VisionSettings } from '@/types/app'
import { Resolution, TransferMethod } from '@/types/app' import { Resolution, TransferMethod } from '@/types/app'
import { useAppFavicon } from '@/hooks/use-app-favicon' import { useAppFavicon } from '@/hooks/use-app-favicon'
import { useGetAppAccessMode, useGetUserCanAccessApp } from '@/service/access-control'
import { AccessMode } from '@/models/access-control'
const GROUP_SIZE = 5 // to avoid RPM(Request per minute) limit. The group task finished then the next group. const GROUP_SIZE = 5 // to avoid RPM(Request per minute) limit. The group task finished then the next group.
enum TaskStatus { enum TaskStatus {
@ -61,12 +64,14 @@ export type IMainProps = {
isInstalledApp?: boolean isInstalledApp?: boolean
installedAppInfo?: InstalledApp installedAppInfo?: InstalledApp
isWorkflow?: boolean isWorkflow?: boolean
isFromExplore?: boolean
} }
const TextGeneration: FC<IMainProps> = ({ const TextGeneration: FC<IMainProps> = ({
isInstalledApp = false, isInstalledApp = false,
installedAppInfo, installedAppInfo,
isWorkflow = false, isWorkflow = false,
isFromExplore = false,
}) => { }) => {
const { notify } = Toast const { notify } = Toast
@ -107,6 +112,9 @@ const TextGeneration: FC<IMainProps> = ({
const [moreLikeThisConfig, setMoreLikeThisConfig] = useState<MoreLikeThisConfig | null>(null) const [moreLikeThisConfig, setMoreLikeThisConfig] = useState<MoreLikeThisConfig | null>(null)
const [textToSpeechConfig, setTextToSpeechConfig] = useState<TextToSpeechConfig | null>(null) const [textToSpeechConfig, setTextToSpeechConfig] = useState<TextToSpeechConfig | null>(null)
const { isPending: isGettingAccessMode, data: appAccessMode } = useGetAppAccessMode(appId)
const { isPending: isCheckingPermission, data: userCanAccessResult } = useGetUserCanAccessApp(appId)
// save message // save message
const [savedMessages, setSavedMessages] = useState<SavedMessage[]>([]) const [savedMessages, setSavedMessages] = useState<SavedMessage[]>([])
const fetchSavedMessage = async () => { const fetchSavedMessage = async () => {
@ -538,12 +546,14 @@ const TextGeneration: FC<IMainProps> = ({
</div> </div>
) )
if (!appId || !siteInfo || !promptConfig) { if (!appId || !siteInfo || !promptConfig || isGettingAccessMode || isCheckingPermission) {
return ( return (
<div className='flex items-center h-screen'> <div className='flex items-center h-screen'>
<Loading type='app' /> <Loading type='app' />
</div>) </div>)
} }
if (!userCanAccessResult?.result)
return <AppUnavailable code={403} unknownReason='no permission.' />
return ( return (
<> <>
@ -571,7 +581,7 @@ const TextGeneration: FC<IMainProps> = ({
/> />
<div className='text-lg font-semibold text-gray-800'>{siteInfo.title}</div> <div className='text-lg font-semibold text-gray-800'>{siteInfo.title}</div>
</div> </div>
<MenuDropdown /> <MenuDropdown hideLogout={isFromExplore || appAccessMode?.accessMode === AccessMode.PUBLIC} />
</div> </div>
{!isPC && ( {!isPC && (
<Button <Button

View File

@ -22,11 +22,13 @@ import cn from '@/utils/classnames'
type Props = { type Props = {
data?: SiteInfo data?: SiteInfo
placement?: Placement placement?: Placement
hideLogout?: boolean
} }
const MenuDropdown: FC<Props> = ({ const MenuDropdown: FC<Props> = ({
data, data,
placement, placement,
hideLogout,
}) => { }) => {
const router = useRouter() const router = useRouter()
const { t } = useTranslation() const { t } = useTranslation()
@ -44,7 +46,7 @@ const MenuDropdown: FC<Props> = ({
const handleLogout = useCallback(() => { const handleLogout = useCallback(() => {
removeAccessToken() removeAccessToken()
router.replace(`/webapp-signin?redirect_url=${window.location.href}`) router.replace(`/webapp-signin?redirect_url=${window.location.href}`)
}, []) }, [router])
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
@ -81,13 +83,17 @@ const MenuDropdown: FC<Props> = ({
}} }}
className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
>{t('common.userProfile.about')}</div> >{t('common.userProfile.about')}</div>
<Divider /> {!hideLogout && (
<div <>
onClick={() => { <Divider />
handleLogout() <div
}} onClick={() => {
className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-destructive hover:bg-state-base-hover' handleLogout()
>{t('common.userProfile.logout')}</div> }}
className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-destructive hover:bg-state-base-hover'
>{t('common.userProfile.logout')}</div>
</>
)}
</div> </div>
</div> </div>
</PortalToFollowElemContent> </PortalToFollowElemContent>

View File

@ -9,7 +9,8 @@ export default function useDocumentTitle(title: string) {
if (systemFeatures.branding.enabled) { if (systemFeatures.branding.enabled) {
document.title = `${prefix}${systemFeatures.branding.application_title}` document.title = `${prefix}${systemFeatures.branding.application_title}`
const faviconEle = document.querySelector('link[rel*=\'icon\']') as HTMLLinkElement const faviconEle = document.querySelector('link[rel*=\'icon\']') as HTMLLinkElement
faviconEle.href = systemFeatures.branding.favicon if (faviconEle)
faviconEle.href = systemFeatures.branding.favicon
} }
else { else {
document.title = `${prefix}Dify` document.title = `${prefix}Dify`

View File

@ -63,3 +63,19 @@ export const useUpdateAccessMode = () => {
}, },
}) })
} }
export const useGetAppAccessMode = (appId?: string) => {
return useQuery({
queryKey: [NAME_SPACE, 'app-access-mode', appId],
queryFn: () => get<{ accessMode: AccessMode }>(`/enterprise/webapp/app/access-mode?appId=${appId}`),
enabled: !!appId,
})
}
export const useGetUserCanAccessApp = (appId?: string) => {
return useQuery({
queryKey: [NAME_SPACE, 'user-can-access-app', appId],
queryFn: () => get<{ result: boolean }>(`/enterprise/webapp/permission?appId=${appId}`),
enabled: !!appId,
})
}