Perf/web app authrozation (#22524)
This commit is contained in:
@@ -18,11 +18,8 @@ import type {
|
||||
import { noop } from 'lodash-es'
|
||||
|
||||
export type ChatWithHistoryContextValue = {
|
||||
appInfoError?: any
|
||||
appInfoLoading?: boolean
|
||||
appMeta?: AppMeta
|
||||
appData?: AppData
|
||||
userCanAccess?: boolean
|
||||
appMeta?: AppMeta | null
|
||||
appData?: AppData | null
|
||||
appParams?: ChatConfig
|
||||
appChatListDataLoading?: boolean
|
||||
currentConversationId: string
|
||||
@@ -62,7 +59,6 @@ export type ChatWithHistoryContextValue = {
|
||||
}
|
||||
|
||||
export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({
|
||||
userCanAccess: false,
|
||||
currentConversationId: '',
|
||||
appPrevChatTree: [],
|
||||
pinnedConversationList: [],
|
||||
|
||||
@@ -21,9 +21,6 @@ import { addFileInfos, sortAgentSorts } from '../../../tools/utils'
|
||||
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
|
||||
import {
|
||||
delConversation,
|
||||
fetchAppInfo,
|
||||
fetchAppMeta,
|
||||
fetchAppParams,
|
||||
fetchChatList,
|
||||
fetchConversations,
|
||||
generationConversationName,
|
||||
@@ -43,8 +40,7 @@ import { useAppFavicon } from '@/hooks/use-app-favicon'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { noop } from 'lodash-es'
|
||||
import { useGetUserCanAccessApp } from '@/service/access-control'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
|
||||
function getFormattedChatList(messages: any[]) {
|
||||
const newChatList: ChatItem[] = []
|
||||
@@ -74,13 +70,9 @@ function getFormattedChatList(messages: any[]) {
|
||||
|
||||
export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
||||
const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo])
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR(installedAppInfo ? null : 'appInfo', fetchAppInfo)
|
||||
const { isPending: isCheckingPermission, data: userCanAccessResult } = useGetUserCanAccessApp({
|
||||
appId: installedAppInfo?.app.id || appInfo?.app_id,
|
||||
isInstalledApp,
|
||||
enabled: systemFeatures.webapp_auth.enabled,
|
||||
})
|
||||
const appInfo = useWebAppStore(s => s.appInfo)
|
||||
const appParams = useWebAppStore(s => s.appParams)
|
||||
const appMeta = useWebAppStore(s => s.appMeta)
|
||||
|
||||
useAppFavicon({
|
||||
enable: !installedAppInfo,
|
||||
@@ -107,6 +99,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
||||
use_icon_as_answer_icon: app.use_icon_as_answer_icon,
|
||||
},
|
||||
plan: 'basic',
|
||||
custom_config: null,
|
||||
} as AppData
|
||||
}
|
||||
|
||||
@@ -166,8 +159,6 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
||||
return currentConversationId
|
||||
}, [currentConversationId, newConversationId])
|
||||
|
||||
const { data: appParams } = useSWR(['appParams', isInstalledApp, appId], () => fetchAppParams(isInstalledApp, appId))
|
||||
const { data: appMeta } = useSWR(['appMeta', isInstalledApp, appId], () => fetchAppMeta(isInstalledApp, appId))
|
||||
const { data: appPinnedConversationData, mutate: mutateAppPinnedConversationData } = useSWR(['appConversationData', isInstalledApp, appId, true], () => fetchConversations(isInstalledApp, appId, undefined, true, 100))
|
||||
const { data: appConversationData, isLoading: appConversationDataLoading, mutate: mutateAppConversationData } = useSWR(['appConversationData', isInstalledApp, appId, false], () => fetchConversations(isInstalledApp, appId, undefined, false, 100))
|
||||
const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null, () => fetchChatList(chatShouldReloadKey, isInstalledApp, appId))
|
||||
@@ -485,9 +476,6 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
||||
}, [isInstalledApp, appId, t, notify])
|
||||
|
||||
return {
|
||||
appInfoError,
|
||||
appInfoLoading: appInfoLoading || (systemFeatures.webapp_auth.enabled && isCheckingPermission),
|
||||
userCanAccess: systemFeatures.webapp_auth.enabled ? userCanAccessResult?.result : true,
|
||||
isInstalledApp,
|
||||
appId,
|
||||
currentConversationId,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
@@ -19,12 +18,10 @@ import ChatWrapper from './chat-wrapper'
|
||||
import type { InstalledApp } from '@/models/explore'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { checkOrSetAccessToken, removeAccessToken } from '@/app/components/share/utils'
|
||||
import { checkOrSetAccessToken } from '@/app/components/share/utils'
|
||||
import AppUnavailable from '@/app/components/base/app-unavailable'
|
||||
import cn from '@/utils/classnames'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
|
||||
type ChatWithHistoryProps = {
|
||||
className?: string
|
||||
@@ -33,16 +30,12 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
||||
className,
|
||||
}) => {
|
||||
const {
|
||||
userCanAccess,
|
||||
appInfoError,
|
||||
appData,
|
||||
appInfoLoading,
|
||||
appChatListDataLoading,
|
||||
chatShouldReloadKey,
|
||||
isMobile,
|
||||
themeBuilder,
|
||||
sidebarCollapseState,
|
||||
isInstalledApp,
|
||||
} = useChatWithHistoryContext()
|
||||
const isSidebarCollapsed = sidebarCollapseState
|
||||
const customConfig = appData?.custom_config
|
||||
@@ -56,41 +49,6 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
||||
|
||||
useDocumentTitle(site?.title || 'Chat')
|
||||
|
||||
const { t } = useTranslation()
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const getSigninUrl = useCallback(() => {
|
||||
const params = new URLSearchParams(searchParams)
|
||||
params.delete('message')
|
||||
params.set('redirect_url', pathname)
|
||||
return `/webapp-signin?${params.toString()}`
|
||||
}, [searchParams, pathname])
|
||||
|
||||
const backToHome = useCallback(() => {
|
||||
removeAccessToken()
|
||||
const url = getSigninUrl()
|
||||
router.replace(url)
|
||||
}, [getSigninUrl, router])
|
||||
|
||||
if (appInfoLoading) {
|
||||
return (
|
||||
<Loading type='app' />
|
||||
)
|
||||
}
|
||||
if (!userCanAccess) {
|
||||
return <div className='flex h-full flex-col items-center justify-center gap-y-2'>
|
||||
<AppUnavailable className='h-auto w-auto' code={403} unknownReason='no permission.' />
|
||||
{!isInstalledApp && <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('common.userProfile.logout')}</span>}
|
||||
</div>
|
||||
}
|
||||
|
||||
if (appInfoError) {
|
||||
return (
|
||||
<AppUnavailable />
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex h-full bg-background-default-burn',
|
||||
@@ -148,9 +106,6 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
|
||||
const themeBuilder = useThemeContext()
|
||||
|
||||
const {
|
||||
appInfoError,
|
||||
appInfoLoading,
|
||||
userCanAccess,
|
||||
appData,
|
||||
appParams,
|
||||
appMeta,
|
||||
@@ -191,10 +146,7 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
|
||||
|
||||
return (
|
||||
<ChatWithHistoryContext.Provider value={{
|
||||
appInfoError,
|
||||
appInfoLoading,
|
||||
appData,
|
||||
userCanAccess,
|
||||
appParams,
|
||||
appMeta,
|
||||
appChatListDataLoading,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
'use client'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useAsyncEffect } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
EmbeddedChatbotContext,
|
||||
@@ -14,8 +11,6 @@ import { useEmbeddedChatbot } from './hooks'
|
||||
import { isDify } from './utils'
|
||||
import { useThemeContext } from './theme/theme-context'
|
||||
import { CssTransform } from './theme/utils'
|
||||
import { checkOrSetAccessToken, removeAccessToken } from '@/app/components/share/utils'
|
||||
import AppUnavailable from '@/app/components/base/app-unavailable'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import LogoHeader from '@/app/components/base/logo/logo-embedded-chat-header'
|
||||
@@ -25,21 +20,16 @@ import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import cn from '@/utils/classnames'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
|
||||
const Chatbot = () => {
|
||||
const {
|
||||
userCanAccess,
|
||||
isMobile,
|
||||
allowResetChat,
|
||||
appInfoError,
|
||||
appInfoLoading,
|
||||
appData,
|
||||
appChatListDataLoading,
|
||||
chatShouldReloadKey,
|
||||
handleNewConversation,
|
||||
themeBuilder,
|
||||
isInstalledApp,
|
||||
} = useEmbeddedChatbotContext()
|
||||
const { t } = useTranslation()
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
@@ -55,58 +45,6 @@ const Chatbot = () => {
|
||||
|
||||
useDocumentTitle(site?.title || 'Chat')
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const getSigninUrl = useCallback(() => {
|
||||
const params = new URLSearchParams(searchParams)
|
||||
params.delete('message')
|
||||
params.set('redirect_url', pathname)
|
||||
return `/webapp-signin?${params.toString()}`
|
||||
}, [searchParams, pathname])
|
||||
|
||||
const backToHome = useCallback(() => {
|
||||
removeAccessToken()
|
||||
const url = getSigninUrl()
|
||||
router.replace(url)
|
||||
}, [getSigninUrl, router])
|
||||
|
||||
if (appInfoLoading) {
|
||||
return (
|
||||
<>
|
||||
{!isMobile && <Loading type='app' />}
|
||||
{isMobile && (
|
||||
<div className={cn('relative')}>
|
||||
<div className={cn('flex h-[calc(100vh_-_60px)] flex-col rounded-2xl border-[0.5px] border-components-panel-border shadow-xs')}>
|
||||
<Loading type='app' />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (!userCanAccess) {
|
||||
return <div className='flex h-full flex-col items-center justify-center gap-y-2'>
|
||||
<AppUnavailable className='h-auto w-auto' code={403} unknownReason='no permission.' />
|
||||
{!isInstalledApp && <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('common.userProfile.logout')}</span>}
|
||||
</div>
|
||||
}
|
||||
|
||||
if (appInfoError) {
|
||||
return (
|
||||
<>
|
||||
{!isMobile && <AppUnavailable />}
|
||||
{isMobile && (
|
||||
<div className={cn('relative')}>
|
||||
<div className={cn('flex h-[calc(100vh_-_60px)] flex-col rounded-2xl border-[0.5px] border-components-panel-border shadow-xs')}>
|
||||
<AppUnavailable />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className='relative'>
|
||||
<div
|
||||
@@ -162,8 +100,6 @@ const EmbeddedChatbotWrapper = () => {
|
||||
const themeBuilder = useThemeContext()
|
||||
|
||||
const {
|
||||
appInfoError,
|
||||
appInfoLoading,
|
||||
appData,
|
||||
userCanAccess,
|
||||
appParams,
|
||||
@@ -200,8 +136,6 @@ const EmbeddedChatbotWrapper = () => {
|
||||
|
||||
return <EmbeddedChatbotContext.Provider value={{
|
||||
userCanAccess,
|
||||
appInfoError,
|
||||
appInfoLoading,
|
||||
appData,
|
||||
appParams,
|
||||
appMeta,
|
||||
@@ -241,34 +175,6 @@ const EmbeddedChatbotWrapper = () => {
|
||||
}
|
||||
|
||||
const EmbeddedChatbot = () => {
|
||||
const [initialized, setInitialized] = useState(false)
|
||||
const [appUnavailable, setAppUnavailable] = useState<boolean>(false)
|
||||
const [isUnknownReason, setIsUnknownReason] = useState<boolean>(false)
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (!initialized) {
|
||||
try {
|
||||
await checkOrSetAccessToken()
|
||||
}
|
||||
catch (e: any) {
|
||||
if (e.status === 404) {
|
||||
setAppUnavailable(true)
|
||||
}
|
||||
else {
|
||||
setIsUnknownReason(true)
|
||||
setAppUnavailable(true)
|
||||
}
|
||||
}
|
||||
setInitialized(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (!initialized)
|
||||
return null
|
||||
|
||||
if (appUnavailable)
|
||||
return <AppUnavailable isUnknownReason={isUnknownReason} />
|
||||
|
||||
return <EmbeddedChatbotWrapper />
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,16 @@ export type ChatConfig = Omit<ModelConfig, 'model'> & {
|
||||
questionEditEnable?: boolean
|
||||
supportFeedback?: boolean
|
||||
supportCitationHitInfo?: boolean
|
||||
system_parameters: {
|
||||
audio_file_size_limit: number
|
||||
file_size_limit: number
|
||||
image_file_size_limit: number
|
||||
video_file_size_limit: number
|
||||
workflow_file_upload_limit: number
|
||||
}
|
||||
more_like_this: {
|
||||
enabled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export type WorkflowProcess = {
|
||||
|
||||
@@ -22,6 +22,7 @@ const Explore: FC<IExploreProps> = ({
|
||||
const { userProfile, isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
const [hasEditPermission, setHasEditPermission] = useState(false)
|
||||
const [installedApps, setInstalledApps] = useState<InstalledApp[]>([])
|
||||
const [isFetchingInstalledApps, setIsFetchingInstalledApps] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
|
||||
useDocumentTitle(t('common.menus.explore'))
|
||||
@@ -51,6 +52,8 @@ const Explore: FC<IExploreProps> = ({
|
||||
hasEditPermission,
|
||||
installedApps,
|
||||
setInstalledApps,
|
||||
isFetchingInstalledApps,
|
||||
setIsFetchingInstalledApps,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import React from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import ExploreContext from '@/context/explore-context'
|
||||
import TextGenerationApp from '@/app/components/share/text-generation'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import ChatWithHistory from '@/app/components/base/chat/chat-with-history'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
import AppUnavailable from '../../base/app-unavailable'
|
||||
import { useGetUserCanAccessApp } from '@/service/access-control'
|
||||
import { useGetInstalledAppAccessModeByAppId, useGetInstalledAppMeta, useGetInstalledAppParams } from '@/service/use-explore'
|
||||
import type { AppData } from '@/models/share'
|
||||
|
||||
export type IInstalledAppProps = {
|
||||
id: string
|
||||
@@ -14,26 +20,95 @@ export type IInstalledAppProps = {
|
||||
const InstalledApp: FC<IInstalledAppProps> = ({
|
||||
id,
|
||||
}) => {
|
||||
const { installedApps } = useContext(ExploreContext)
|
||||
const { installedApps, isFetchingInstalledApps } = useContext(ExploreContext)
|
||||
const updateAppInfo = useWebAppStore(s => s.updateAppInfo)
|
||||
const installedApp = installedApps.find(item => item.id === id)
|
||||
const updateWebAppAccessMode = useWebAppStore(s => s.updateWebAppAccessMode)
|
||||
const updateAppParams = useWebAppStore(s => s.updateAppParams)
|
||||
const updateWebAppMeta = useWebAppStore(s => s.updateWebAppMeta)
|
||||
const updateUserCanAccessApp = useWebAppStore(s => s.updateUserCanAccessApp)
|
||||
const { isFetching: isFetchingWebAppAccessMode, data: webAppAccessMode, error: webAppAccessModeError } = useGetInstalledAppAccessModeByAppId(installedApp?.id ?? null)
|
||||
const { isFetching: isFetchingAppParams, data: appParams, error: appParamsError } = useGetInstalledAppParams(installedApp?.id ?? null)
|
||||
const { isFetching: isFetchingAppMeta, data: appMeta, error: appMetaError } = useGetInstalledAppMeta(installedApp?.id ?? null)
|
||||
const { data: userCanAccessApp, error: useCanAccessAppError } = useGetUserCanAccessApp({ appId: installedApp?.app.id, isInstalledApp: true })
|
||||
|
||||
if (!installedApp) {
|
||||
return (
|
||||
<div className='flex h-full items-center'>
|
||||
<Loading type='area' />
|
||||
</div>
|
||||
)
|
||||
useEffect(() => {
|
||||
if (!installedApp) {
|
||||
updateAppInfo(null)
|
||||
}
|
||||
else {
|
||||
const { id, app } = installedApp
|
||||
updateAppInfo({
|
||||
app_id: id,
|
||||
site: {
|
||||
title: app.name,
|
||||
icon_type: app.icon_type,
|
||||
icon: app.icon,
|
||||
icon_background: app.icon_background,
|
||||
icon_url: app.icon_url,
|
||||
prompt_public: false,
|
||||
copyright: '',
|
||||
show_workflow_steps: true,
|
||||
use_icon_as_answer_icon: app.use_icon_as_answer_icon,
|
||||
},
|
||||
plan: 'basic',
|
||||
custom_config: null,
|
||||
} as AppData)
|
||||
}
|
||||
|
||||
if (appParams)
|
||||
updateAppParams(appParams)
|
||||
if (appMeta)
|
||||
updateWebAppMeta(appMeta)
|
||||
if (webAppAccessMode)
|
||||
updateWebAppAccessMode(webAppAccessMode.accessMode)
|
||||
updateUserCanAccessApp(Boolean(userCanAccessApp && userCanAccessApp?.result))
|
||||
}, [installedApp, appMeta, appParams, updateAppInfo, updateAppParams, updateUserCanAccessApp, updateWebAppMeta, userCanAccessApp, webAppAccessMode, updateWebAppAccessMode])
|
||||
|
||||
if (appParamsError) {
|
||||
return <div className='flex h-full items-center justify-center'>
|
||||
<AppUnavailable unknownReason={appParamsError.message} />
|
||||
</div>
|
||||
}
|
||||
if (appMetaError) {
|
||||
return <div className='flex h-full items-center justify-center'>
|
||||
<AppUnavailable unknownReason={appMetaError.message} />
|
||||
</div>
|
||||
}
|
||||
if (useCanAccessAppError) {
|
||||
return <div className='flex h-full items-center justify-center'>
|
||||
<AppUnavailable unknownReason={useCanAccessAppError.message} />
|
||||
</div>
|
||||
}
|
||||
if (webAppAccessModeError) {
|
||||
return <div className='flex h-full items-center justify-center'>
|
||||
<AppUnavailable unknownReason={webAppAccessModeError.message} />
|
||||
</div>
|
||||
}
|
||||
if (userCanAccessApp && !userCanAccessApp.result) {
|
||||
return <div className='flex h-full flex-col items-center justify-center gap-y-2'>
|
||||
<AppUnavailable className='h-auto w-auto' code={403} unknownReason='no permission.' />
|
||||
</div>
|
||||
}
|
||||
if (isFetchingAppParams || isFetchingAppMeta || isFetchingWebAppAccessMode || isFetchingInstalledApps) {
|
||||
return <div className='flex h-full items-center justify-center'>
|
||||
<Loading />
|
||||
</div>
|
||||
}
|
||||
if (!installedApp) {
|
||||
return <div className='flex h-full items-center justify-center'>
|
||||
<AppUnavailable code={404} isUnknownReason />
|
||||
</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='h-full bg-background-default py-2 pl-0 pr-2 sm:p-2'>
|
||||
{installedApp.app.mode !== 'completion' && installedApp.app.mode !== 'workflow' && (
|
||||
{installedApp?.app.mode !== 'completion' && installedApp?.app.mode !== 'workflow' && (
|
||||
<ChatWithHistory installedAppInfo={installedApp} className='overflow-hidden rounded-2xl shadow-md' />
|
||||
)}
|
||||
{installedApp.app.mode === 'completion' && (
|
||||
{installedApp?.app.mode === 'completion' && (
|
||||
<TextGenerationApp isInstalledApp installedAppInfo={installedApp} />
|
||||
)}
|
||||
{installedApp.app.mode === 'workflow' && (
|
||||
{installedApp?.app.mode === 'workflow' && (
|
||||
<TextGenerationApp isWorkflow isInstalledApp installedAppInfo={installedApp} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -8,11 +8,11 @@ import Link from 'next/link'
|
||||
import Toast from '../../base/toast'
|
||||
import Item from './app-nav-item'
|
||||
import cn from '@/utils/classnames'
|
||||
import { fetchInstalledAppList as doFetchInstalledAppList, uninstallApp, updatePinStatus } from '@/service/explore'
|
||||
import ExploreContext from '@/context/explore-context'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { useGetInstalledApps, useUninstallApp, useUpdateAppPinStatus } from '@/service/use-explore'
|
||||
|
||||
const SelectedDiscoveryIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="current" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -50,16 +50,14 @@ const SideBar: FC<IExploreSideBarProps> = ({
|
||||
const lastSegment = segments.slice(-1)[0]
|
||||
const isDiscoverySelected = lastSegment === 'apps'
|
||||
const isChatSelected = lastSegment === 'chat'
|
||||
const { installedApps, setInstalledApps } = useContext(ExploreContext)
|
||||
const { installedApps, setInstalledApps, setIsFetchingInstalledApps } = useContext(ExploreContext)
|
||||
const { isFetching: isFetchingInstalledApps, data: ret, refetch: fetchInstalledAppList } = useGetInstalledApps()
|
||||
const { mutateAsync: uninstallApp } = useUninstallApp()
|
||||
const { mutateAsync: updatePinStatus } = useUpdateAppPinStatus()
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const fetchInstalledAppList = async () => {
|
||||
const { installed_apps }: any = await doFetchInstalledAppList()
|
||||
setInstalledApps(installed_apps)
|
||||
}
|
||||
|
||||
const [showConfirm, setShowConfirm] = useState(false)
|
||||
const [currId, setCurrId] = useState('')
|
||||
const handleDelete = async () => {
|
||||
@@ -70,25 +68,31 @@ const SideBar: FC<IExploreSideBarProps> = ({
|
||||
type: 'success',
|
||||
message: t('common.api.remove'),
|
||||
})
|
||||
fetchInstalledAppList()
|
||||
}
|
||||
|
||||
const handleUpdatePinStatus = async (id: string, isPinned: boolean) => {
|
||||
await updatePinStatus(id, isPinned)
|
||||
await updatePinStatus({ appId: id, isPinned })
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.success'),
|
||||
})
|
||||
fetchInstalledAppList()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchInstalledAppList()
|
||||
}, [])
|
||||
const installed_apps = (ret as any)?.installed_apps
|
||||
if (installed_apps && installed_apps.length > 0)
|
||||
setInstalledApps(installed_apps)
|
||||
else
|
||||
setInstalledApps([])
|
||||
}, [ret, setInstalledApps])
|
||||
|
||||
useEffect(() => {
|
||||
setIsFetchingInstalledApps(isFetchingInstalledApps)
|
||||
}, [isFetchingInstalledApps, setIsFetchingInstalledApps])
|
||||
|
||||
useEffect(() => {
|
||||
fetchInstalledAppList()
|
||||
}, [controlUpdateInstalledApps])
|
||||
}, [controlUpdateInstalledApps, fetchInstalledAppList])
|
||||
|
||||
const pinnedAppsCount = installedApps.filter(({ is_pinned }) => is_pinned).length
|
||||
return (
|
||||
|
||||
@@ -7,16 +7,14 @@ import {
|
||||
RiErrorWarningFill,
|
||||
} from '@remixicon/react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import TabHeader from '../../base/tab-header'
|
||||
import { checkOrSetAccessToken, removeAccessToken } from '../utils'
|
||||
import MenuDropdown from './menu-dropdown'
|
||||
import RunBatch from './run-batch'
|
||||
import ResDownload from './run-batch/res-download'
|
||||
import AppUnavailable from '../../base/app-unavailable'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import RunOnce from '@/app/components/share/text-generation/run-once'
|
||||
import { fetchSavedMessage as doFetchSavedMessage, fetchAppInfo, fetchAppParams, removeMessage, saveMessage } from '@/service/share'
|
||||
import { fetchSavedMessage as doFetchSavedMessage, removeMessage, saveMessage } from '@/service/share'
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import type {
|
||||
MoreLikeThisConfig,
|
||||
@@ -39,10 +37,10 @@ import { Resolution, TransferMethod } from '@/types/app'
|
||||
import { useAppFavicon } from '@/hooks/use-app-favicon'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useGetAppAccessMode, useGetUserCanAccessApp } from '@/service/access-control'
|
||||
import { AccessMode } from '@/models/access-control'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
|
||||
const GROUP_SIZE = 5 // to avoid RPM(Request per minute) limit. The group task finished then the next group.
|
||||
enum TaskStatus {
|
||||
@@ -83,9 +81,6 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
const mode = searchParams.get('mode') || 'create'
|
||||
const [currentTab, setCurrentTab] = useState<string>(['create', 'batch'].includes(mode) ? mode : 'create')
|
||||
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
// Notice this situation isCallBatchAPI but not in batch tab
|
||||
const [isCallBatchAPI, setIsCallBatchAPI] = useState(false)
|
||||
const isInBatchTab = currentTab === 'batch'
|
||||
@@ -103,30 +98,19 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
const [moreLikeThisConfig, setMoreLikeThisConfig] = useState<MoreLikeThisConfig | null>(null)
|
||||
const [textToSpeechConfig, setTextToSpeechConfig] = useState<TextToSpeechConfig | null>(null)
|
||||
|
||||
const { isPending: isGettingAccessMode, data: appAccessMode } = useGetAppAccessMode({
|
||||
appId,
|
||||
isInstalledApp,
|
||||
enabled: systemFeatures.webapp_auth.enabled,
|
||||
})
|
||||
const { isPending: isCheckingPermission, data: userCanAccessResult } = useGetUserCanAccessApp({
|
||||
appId,
|
||||
isInstalledApp,
|
||||
enabled: systemFeatures.webapp_auth.enabled,
|
||||
})
|
||||
|
||||
// save message
|
||||
const [savedMessages, setSavedMessages] = useState<SavedMessage[]>([])
|
||||
const fetchSavedMessage = async () => {
|
||||
const res: any = await doFetchSavedMessage(isInstalledApp, installedAppInfo?.id)
|
||||
const fetchSavedMessage = useCallback(async () => {
|
||||
const res: any = await doFetchSavedMessage(isInstalledApp, appId)
|
||||
setSavedMessages(res.data)
|
||||
}
|
||||
}, [isInstalledApp, appId])
|
||||
const handleSaveMessage = async (messageId: string) => {
|
||||
await saveMessage(messageId, isInstalledApp, installedAppInfo?.id)
|
||||
await saveMessage(messageId, isInstalledApp, appId)
|
||||
notify({ type: 'success', message: t('common.api.saved') })
|
||||
fetchSavedMessage()
|
||||
}
|
||||
const handleRemoveSavedMessage = async (messageId: string) => {
|
||||
await removeMessage(messageId, isInstalledApp, installedAppInfo?.id)
|
||||
await removeMessage(messageId, isInstalledApp, appId)
|
||||
notify({ type: 'success', message: t('common.api.remove') })
|
||||
fetchSavedMessage()
|
||||
}
|
||||
@@ -375,34 +359,14 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const fetchInitData = async () => {
|
||||
if (!isInstalledApp)
|
||||
await checkOrSetAccessToken()
|
||||
|
||||
return Promise.all([
|
||||
isInstalledApp
|
||||
? {
|
||||
app_id: installedAppInfo?.id,
|
||||
site: {
|
||||
title: installedAppInfo?.app.name,
|
||||
prompt_public: false,
|
||||
copyright: '',
|
||||
icon: installedAppInfo?.app.icon,
|
||||
icon_background: installedAppInfo?.app.icon_background,
|
||||
},
|
||||
plan: 'basic',
|
||||
}
|
||||
: fetchAppInfo(),
|
||||
fetchAppParams(isInstalledApp, installedAppInfo?.id),
|
||||
!isWorkflow
|
||||
? fetchSavedMessage()
|
||||
: {},
|
||||
])
|
||||
}
|
||||
|
||||
const appData = useWebAppStore(s => s.appInfo)
|
||||
const appParams = useWebAppStore(s => s.appParams)
|
||||
const accessMode = useWebAppStore(s => s.webAppAccessMode)
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const [appData, appParams]: any = await fetchInitData()
|
||||
if (!appData || !appParams)
|
||||
return
|
||||
!isWorkflow && fetchSavedMessage()
|
||||
const { app_id: appId, site: siteInfo, custom_config } = appData
|
||||
setAppId(appId)
|
||||
setSiteInfo(siteInfo as SiteInfo)
|
||||
@@ -413,11 +377,11 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
setVisionConfig({
|
||||
// legacy of image upload compatible
|
||||
...file_upload,
|
||||
transfer_methods: file_upload.allowed_file_upload_methods || file_upload.allowed_upload_methods,
|
||||
transfer_methods: file_upload?.allowed_file_upload_methods || file_upload?.allowed_upload_methods,
|
||||
// legacy of image upload compatible
|
||||
image_file_size_limit: appParams?.system_parameters?.image_file_size_limit,
|
||||
image_file_size_limit: appParams?.system_parameters.image_file_size_limit,
|
||||
fileUploadConfig: appParams?.system_parameters,
|
||||
})
|
||||
} as any)
|
||||
const prompt_variables = userInputsFormToPromptVariables(user_input_form)
|
||||
setPromptConfig({
|
||||
prompt_template: '', // placeholder for future
|
||||
@@ -426,7 +390,7 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
setMoreLikeThisConfig(more_like_this)
|
||||
setTextToSpeechConfig(text_to_speech)
|
||||
})()
|
||||
}, [])
|
||||
}, [appData, appParams, fetchSavedMessage, isWorkflow])
|
||||
|
||||
// Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client.
|
||||
useDocumentTitle(siteInfo?.title || t('share.generation.title'))
|
||||
@@ -528,32 +492,12 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
</div>
|
||||
)
|
||||
|
||||
const getSigninUrl = useCallback(() => {
|
||||
const params = new URLSearchParams(searchParams)
|
||||
params.delete('message')
|
||||
params.set('redirect_url', pathname)
|
||||
return `/webapp-signin?${params.toString()}`
|
||||
}, [searchParams, pathname])
|
||||
|
||||
const backToHome = useCallback(() => {
|
||||
removeAccessToken()
|
||||
const url = getSigninUrl()
|
||||
router.replace(url)
|
||||
}, [getSigninUrl, router])
|
||||
|
||||
if (!appId || !siteInfo || !promptConfig || (systemFeatures.webapp_auth.enabled && (isGettingAccessMode || isCheckingPermission))) {
|
||||
if (!appId || !siteInfo || !promptConfig) {
|
||||
return (
|
||||
<div className='flex h-screen items-center'>
|
||||
<Loading type='app' />
|
||||
</div>)
|
||||
}
|
||||
if (systemFeatures.webapp_auth.enabled && !userCanAccessResult?.result) {
|
||||
return <div className='flex h-full flex-col items-center justify-center gap-y-2'>
|
||||
<AppUnavailable className='h-auto w-auto' code={403} unknownReason='no permission.' />
|
||||
{!isInstalledApp && <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('common.userProfile.logout')}</span>}
|
||||
</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'bg-background-default-burn',
|
||||
@@ -578,7 +522,7 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
imageUrl={siteInfo.icon_url}
|
||||
/>
|
||||
<div className='system-md-semibold grow truncate text-text-secondary'>{siteInfo.title}</div>
|
||||
<MenuDropdown hideLogout={isInstalledApp || appAccessMode?.accessMode === AccessMode.PUBLIC} data={siteInfo} />
|
||||
<MenuDropdown hideLogout={isInstalledApp || accessMode === AccessMode.PUBLIC} data={siteInfo} />
|
||||
</div>
|
||||
{siteInfo.description && (
|
||||
<div className='system-xs-regular text-text-tertiary'>{siteInfo.description}</div>
|
||||
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
import ThemeSwitcher from '@/app/components/base/theme-switcher'
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { AccessMode } from '@/models/access-control'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
|
||||
type Props = {
|
||||
data?: SiteInfo
|
||||
@@ -32,7 +32,7 @@ const MenuDropdown: FC<Props> = ({
|
||||
placement,
|
||||
hideLogout,
|
||||
}) => {
|
||||
const webAppAccessMode = useGlobalPublicStore(s => s.webAppAccessMode)
|
||||
const webAppAccessMode = useWebAppStore(s => s.webAppAccessMode)
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -10,7 +10,7 @@ export const getInitialTokenV2 = (): Record<string, any> => ({
|
||||
version: 2,
|
||||
})
|
||||
|
||||
export const checkOrSetAccessToken = async (appCode?: string) => {
|
||||
export const checkOrSetAccessToken = async (appCode?: string | null) => {
|
||||
const sharedToken = appCode || globalThis.location.pathname.split('/').slice(-1)[0]
|
||||
const userId = (await getProcessedSystemVariablesFromUrlParams()).user_id
|
||||
const accessToken = localStorage.getItem('token') || JSON.stringify(getInitialTokenV2())
|
||||
|
||||
Reference in New Issue
Block a user