Perf/web app authrozation (#22524)

This commit is contained in:
NFish
2025-07-17 10:52:10 +08:00
committed by GitHub
parent a3ced1b5a6
commit a324d3942e
35 changed files with 592 additions and 441 deletions

View File

@@ -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: [],

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 />
}

View File

@@ -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 = {

View File

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

View File

@@ -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>

View File

@@ -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 (

View File

@@ -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>

View File

@@ -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()

View File

@@ -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())