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 6.7KB

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