mirror of
				https://github.com/langgenius/dify.git
				synced 2025-10-31 02:42:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			284 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			284 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 'use client'
 | |
| import type { FC } from 'react'
 | |
| import {
 | |
|   useCallback,
 | |
|   useEffect,
 | |
|   useState,
 | |
| } from 'react'
 | |
| import { useAsyncEffect } from 'ahooks'
 | |
| import { useThemeContext } from '../embedded-chatbot/theme/theme-context'
 | |
| import {
 | |
|   ChatWithHistoryContext,
 | |
|   useChatWithHistoryContext,
 | |
| } from './context'
 | |
| import { useChatWithHistory } from './hooks'
 | |
| import Sidebar from './sidebar'
 | |
| import Header from './header'
 | |
| import HeaderInMobile from './header-in-mobile'
 | |
| 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 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
 | |
| }
 | |
| 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
 | |
|   const site = appData?.site
 | |
| 
 | |
|   const [showSidePanel, setShowSidePanel] = useState(false)
 | |
| 
 | |
|   useEffect(() => {
 | |
|     themeBuilder?.buildTheme(site?.chat_color_theme, site?.chat_color_theme_inverted)
 | |
|   }, [site, customConfig, themeBuilder])
 | |
| 
 | |
|   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',
 | |
|       isMobile && 'flex-col',
 | |
|       className,
 | |
|     )}>
 | |
|       {!isMobile && (
 | |
|         <div className={cn(
 | |
|           'flex w-[236px] flex-col p-1 pr-0 transition-all duration-200 ease-in-out',
 | |
|           isSidebarCollapsed && 'w-0 overflow-hidden !p-0',
 | |
|         )}>
 | |
|           <Sidebar />
 | |
|         </div>
 | |
|       )}
 | |
|       {isMobile && (
 | |
|         <HeaderInMobile />
 | |
|       )}
 | |
|       <div className={cn('relative grow p-2', isMobile && 'h-[calc(100%_-_56px)] p-0')}>
 | |
|         {isSidebarCollapsed && (
 | |
|           <div
 | |
|             className={cn(
 | |
|               'absolute top-0 z-20 flex h-full w-[256px] flex-col p-2 transition-all duration-500 ease-in-out',
 | |
|               showSidePanel ? 'left-0' : 'left-[-248px]',
 | |
|             )}
 | |
|             onMouseEnter={() => setShowSidePanel(true)}
 | |
|             onMouseLeave={() => setShowSidePanel(false)}
 | |
|           >
 | |
|             <Sidebar isPanel />
 | |
|           </div>
 | |
|         )}
 | |
|         <div className={cn('flex h-full flex-col overflow-hidden border-[0,5px] border-components-panel-border-subtle bg-chatbot-bg', isMobile ? 'rounded-t-2xl' : 'rounded-2xl')}>
 | |
|           {!isMobile && <Header />}
 | |
|           {appChatListDataLoading && (
 | |
|             <Loading type='app' />
 | |
|           )}
 | |
|           {!appChatListDataLoading && (
 | |
|             <ChatWrapper key={chatShouldReloadKey} />
 | |
|           )}
 | |
|         </div>
 | |
|       </div>
 | |
|     </div>
 | |
|   )
 | |
| }
 | |
| 
 | |
| export type ChatWithHistoryWrapProps = {
 | |
|   installedAppInfo?: InstalledApp
 | |
|   className?: string
 | |
| }
 | |
| const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
 | |
|   installedAppInfo,
 | |
|   className,
 | |
| }) => {
 | |
|   const media = useBreakpoints()
 | |
|   const isMobile = media === MediaType.mobile
 | |
|   const themeBuilder = useThemeContext()
 | |
| 
 | |
|   const {
 | |
|     appInfoError,
 | |
|     appInfoLoading,
 | |
|     userCanAccess,
 | |
|     appData,
 | |
|     appParams,
 | |
|     appMeta,
 | |
|     appChatListDataLoading,
 | |
|     currentConversationId,
 | |
|     currentConversationItem,
 | |
|     appPrevChatTree,
 | |
|     pinnedConversationList,
 | |
|     conversationList,
 | |
|     newConversationInputs,
 | |
|     newConversationInputsRef,
 | |
|     handleNewConversationInputsChange,
 | |
|     inputsForms,
 | |
|     handleNewConversation,
 | |
|     handleStartChat,
 | |
|     handleChangeConversation,
 | |
|     handlePinConversation,
 | |
|     handleUnpinConversation,
 | |
|     handleDeleteConversation,
 | |
|     conversationRenaming,
 | |
|     handleRenameConversation,
 | |
|     handleNewConversationCompleted,
 | |
|     chatShouldReloadKey,
 | |
|     isInstalledApp,
 | |
|     appId,
 | |
|     handleFeedback,
 | |
|     currentChatInstanceRef,
 | |
|     sidebarCollapseState,
 | |
|     handleSidebarCollapse,
 | |
|     clearChatList,
 | |
|     setClearChatList,
 | |
|     isResponding,
 | |
|     setIsResponding,
 | |
|     currentConversationInputs,
 | |
|     setCurrentConversationInputs,
 | |
|     allInputsHidden,
 | |
|   } = useChatWithHistory(installedAppInfo)
 | |
| 
 | |
|   return (
 | |
|     <ChatWithHistoryContext.Provider value={{
 | |
|       appInfoError,
 | |
|       appInfoLoading,
 | |
|       appData,
 | |
|       userCanAccess,
 | |
|       appParams,
 | |
|       appMeta,
 | |
|       appChatListDataLoading,
 | |
|       currentConversationId,
 | |
|       currentConversationItem,
 | |
|       appPrevChatTree,
 | |
|       pinnedConversationList,
 | |
|       conversationList,
 | |
|       newConversationInputs,
 | |
|       newConversationInputsRef,
 | |
|       handleNewConversationInputsChange,
 | |
|       inputsForms,
 | |
|       handleNewConversation,
 | |
|       handleStartChat,
 | |
|       handleChangeConversation,
 | |
|       handlePinConversation,
 | |
|       handleUnpinConversation,
 | |
|       handleDeleteConversation,
 | |
|       conversationRenaming,
 | |
|       handleRenameConversation,
 | |
|       handleNewConversationCompleted,
 | |
|       chatShouldReloadKey,
 | |
|       isMobile,
 | |
|       isInstalledApp,
 | |
|       appId,
 | |
|       handleFeedback,
 | |
|       currentChatInstanceRef,
 | |
|       themeBuilder,
 | |
|       sidebarCollapseState,
 | |
|       handleSidebarCollapse,
 | |
|       clearChatList,
 | |
|       setClearChatList,
 | |
|       isResponding,
 | |
|       setIsResponding,
 | |
|       currentConversationInputs,
 | |
|       setCurrentConversationInputs,
 | |
|       allInputsHidden,
 | |
|     }}>
 | |
|       <ChatWithHistory className={className} />
 | |
|     </ChatWithHistoryContext.Provider>
 | |
|   )
 | |
| }
 | |
| 
 | |
| const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
 | |
|   installedAppInfo,
 | |
|   className,
 | |
| }) => {
 | |
|   const [initialized, setInitialized] = useState(false)
 | |
|   const [appUnavailable, setAppUnavailable] = useState<boolean>(false)
 | |
|   const [isUnknownReason, setIsUnknownReason] = useState<boolean>(false)
 | |
| 
 | |
|   useAsyncEffect(async () => {
 | |
|     if (!initialized) {
 | |
|       if (!installedAppInfo) {
 | |
|         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 (
 | |
|     <ChatWithHistoryWrap
 | |
|       installedAppInfo={installedAppInfo}
 | |
|       className={className}
 | |
|     />
 | |
|   )
 | |
| }
 | |
| 
 | |
| export default ChatWithHistoryWrapWithCheckToken
 | 
