You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.tsx 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. 'use client'
  2. import type { FC } from 'react'
  3. import {
  4. useCallback,
  5. useEffect,
  6. useState,
  7. } from 'react'
  8. import { useAsyncEffect } from 'ahooks'
  9. import { useThemeContext } from '../embedded-chatbot/theme/theme-context'
  10. import {
  11. ChatWithHistoryContext,
  12. useChatWithHistoryContext,
  13. } from './context'
  14. import { useChatWithHistory } from './hooks'
  15. import Sidebar from './sidebar'
  16. import Header from './header'
  17. import HeaderInMobile from './header-in-mobile'
  18. import ChatWrapper from './chat-wrapper'
  19. import type { InstalledApp } from '@/models/explore'
  20. import Loading from '@/app/components/base/loading'
  21. import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
  22. import { checkOrSetAccessToken, removeAccessToken } from '@/app/components/share/utils'
  23. import AppUnavailable from '@/app/components/base/app-unavailable'
  24. import cn from '@/utils/classnames'
  25. import useDocumentTitle from '@/hooks/use-document-title'
  26. import { useTranslation } from 'react-i18next'
  27. import { usePathname, useRouter, useSearchParams } from 'next/navigation'
  28. type ChatWithHistoryProps = {
  29. className?: string
  30. }
  31. const ChatWithHistory: FC<ChatWithHistoryProps> = ({
  32. className,
  33. }) => {
  34. const {
  35. userCanAccess,
  36. appInfoError,
  37. appData,
  38. appInfoLoading,
  39. appChatListDataLoading,
  40. chatShouldReloadKey,
  41. isMobile,
  42. themeBuilder,
  43. sidebarCollapseState,
  44. isInstalledApp,
  45. } = useChatWithHistoryContext()
  46. const isSidebarCollapsed = sidebarCollapseState
  47. const customConfig = appData?.custom_config
  48. const site = appData?.site
  49. const [showSidePanel, setShowSidePanel] = useState(false)
  50. useEffect(() => {
  51. themeBuilder?.buildTheme(site?.chat_color_theme, site?.chat_color_theme_inverted)
  52. }, [site, customConfig, themeBuilder])
  53. useDocumentTitle(site?.title || 'Chat')
  54. const { t } = useTranslation()
  55. const searchParams = useSearchParams()
  56. const router = useRouter()
  57. const pathname = usePathname()
  58. const getSigninUrl = useCallback(() => {
  59. const params = new URLSearchParams(searchParams)
  60. params.delete('message')
  61. params.set('redirect_url', pathname)
  62. return `/webapp-signin?${params.toString()}`
  63. }, [searchParams, pathname])
  64. const backToHome = useCallback(() => {
  65. removeAccessToken()
  66. const url = getSigninUrl()
  67. router.replace(url)
  68. }, [getSigninUrl, router])
  69. if (appInfoLoading) {
  70. return (
  71. <Loading type='app' />
  72. )
  73. }
  74. if (!userCanAccess) {
  75. return <div className='flex h-full flex-col items-center justify-center gap-y-2'>
  76. <AppUnavailable className='h-auto w-auto' code={403} unknownReason='no permission.' />
  77. {!isInstalledApp && <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('common.userProfile.logout')}</span>}
  78. </div>
  79. }
  80. if (appInfoError) {
  81. return (
  82. <AppUnavailable />
  83. )
  84. }
  85. return (
  86. <div className={cn(
  87. 'flex h-full bg-background-default-burn',
  88. isMobile && 'flex-col',
  89. className,
  90. )}>
  91. {!isMobile && (
  92. <div className={cn(
  93. 'flex w-[236px] flex-col p-1 pr-0 transition-all duration-200 ease-in-out',
  94. isSidebarCollapsed && 'w-0 overflow-hidden !p-0',
  95. )}>
  96. <Sidebar />
  97. </div>
  98. )}
  99. {isMobile && (
  100. <HeaderInMobile />
  101. )}
  102. <div className={cn('relative grow p-2', isMobile && 'h-[calc(100%_-_56px)] p-0')}>
  103. {isSidebarCollapsed && (
  104. <div
  105. className={cn(
  106. 'absolute top-0 z-20 flex h-full w-[256px] flex-col p-2 transition-all duration-500 ease-in-out',
  107. showSidePanel ? 'left-0' : 'left-[-248px]',
  108. )}
  109. onMouseEnter={() => setShowSidePanel(true)}
  110. onMouseLeave={() => setShowSidePanel(false)}
  111. >
  112. <Sidebar isPanel />
  113. </div>
  114. )}
  115. <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')}>
  116. {!isMobile && <Header />}
  117. {appChatListDataLoading && (
  118. <Loading type='app' />
  119. )}
  120. {!appChatListDataLoading && (
  121. <ChatWrapper key={chatShouldReloadKey} />
  122. )}
  123. </div>
  124. </div>
  125. </div>
  126. )
  127. }
  128. export type ChatWithHistoryWrapProps = {
  129. installedAppInfo?: InstalledApp
  130. className?: string
  131. }
  132. const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
  133. installedAppInfo,
  134. className,
  135. }) => {
  136. const media = useBreakpoints()
  137. const isMobile = media === MediaType.mobile
  138. const themeBuilder = useThemeContext()
  139. const {
  140. appInfoError,
  141. appInfoLoading,
  142. userCanAccess,
  143. appData,
  144. appParams,
  145. appMeta,
  146. appChatListDataLoading,
  147. currentConversationId,
  148. currentConversationItem,
  149. appPrevChatTree,
  150. pinnedConversationList,
  151. conversationList,
  152. newConversationInputs,
  153. newConversationInputsRef,
  154. handleNewConversationInputsChange,
  155. inputsForms,
  156. handleNewConversation,
  157. handleStartChat,
  158. handleChangeConversation,
  159. handlePinConversation,
  160. handleUnpinConversation,
  161. handleDeleteConversation,
  162. conversationRenaming,
  163. handleRenameConversation,
  164. handleNewConversationCompleted,
  165. chatShouldReloadKey,
  166. isInstalledApp,
  167. appId,
  168. handleFeedback,
  169. currentChatInstanceRef,
  170. sidebarCollapseState,
  171. handleSidebarCollapse,
  172. clearChatList,
  173. setClearChatList,
  174. isResponding,
  175. setIsResponding,
  176. currentConversationInputs,
  177. setCurrentConversationInputs,
  178. allInputsHidden,
  179. } = useChatWithHistory(installedAppInfo)
  180. return (
  181. <ChatWithHistoryContext.Provider value={{
  182. appInfoError,
  183. appInfoLoading,
  184. appData,
  185. userCanAccess,
  186. appParams,
  187. appMeta,
  188. appChatListDataLoading,
  189. currentConversationId,
  190. currentConversationItem,
  191. appPrevChatTree,
  192. pinnedConversationList,
  193. conversationList,
  194. newConversationInputs,
  195. newConversationInputsRef,
  196. handleNewConversationInputsChange,
  197. inputsForms,
  198. handleNewConversation,
  199. handleStartChat,
  200. handleChangeConversation,
  201. handlePinConversation,
  202. handleUnpinConversation,
  203. handleDeleteConversation,
  204. conversationRenaming,
  205. handleRenameConversation,
  206. handleNewConversationCompleted,
  207. chatShouldReloadKey,
  208. isMobile,
  209. isInstalledApp,
  210. appId,
  211. handleFeedback,
  212. currentChatInstanceRef,
  213. themeBuilder,
  214. sidebarCollapseState,
  215. handleSidebarCollapse,
  216. clearChatList,
  217. setClearChatList,
  218. isResponding,
  219. setIsResponding,
  220. currentConversationInputs,
  221. setCurrentConversationInputs,
  222. allInputsHidden,
  223. }}>
  224. <ChatWithHistory className={className} />
  225. </ChatWithHistoryContext.Provider>
  226. )
  227. }
  228. const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
  229. installedAppInfo,
  230. className,
  231. }) => {
  232. const [initialized, setInitialized] = useState(false)
  233. const [appUnavailable, setAppUnavailable] = useState<boolean>(false)
  234. const [isUnknownReason, setIsUnknownReason] = useState<boolean>(false)
  235. useAsyncEffect(async () => {
  236. if (!initialized) {
  237. if (!installedAppInfo) {
  238. try {
  239. await checkOrSetAccessToken()
  240. }
  241. catch (e: any) {
  242. if (e.status === 404) {
  243. setAppUnavailable(true)
  244. }
  245. else {
  246. setIsUnknownReason(true)
  247. setAppUnavailable(true)
  248. }
  249. }
  250. }
  251. setInitialized(true)
  252. }
  253. }, [])
  254. if (!initialized)
  255. return null
  256. if (appUnavailable)
  257. return <AppUnavailable isUnknownReason={isUnknownReason} />
  258. return (
  259. <ChatWithHistoryWrap
  260. installedAppInfo={installedAppInfo}
  261. className={className}
  262. />
  263. )
  264. }
  265. export default ChatWithHistoryWrapWithCheckToken