index.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import {
  2. useEffect,
  3. useState,
  4. } from 'react'
  5. import { useAsyncEffect } from 'ahooks'
  6. import { useTranslation } from 'react-i18next'
  7. import {
  8. EmbeddedChatbotContext,
  9. useEmbeddedChatbotContext,
  10. } from './context'
  11. import { useEmbeddedChatbot } from './hooks'
  12. import { isDify } from './utils'
  13. import { useThemeContext } from './theme/theme-context'
  14. import { CssTransform } from './theme/utils'
  15. import { checkOrSetAccessToken } from '@/app/components/share/utils'
  16. import AppUnavailable from '@/app/components/base/app-unavailable'
  17. import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
  18. import Loading from '@/app/components/base/loading'
  19. import LogoHeader from '@/app/components/base/logo/logo-embedded-chat-header'
  20. import Header from '@/app/components/base/chat/embedded-chatbot/header'
  21. import ChatWrapper from '@/app/components/base/chat/embedded-chatbot/chat-wrapper'
  22. import LogoSite from '@/app/components/base/logo/logo-site'
  23. import cn from '@/utils/classnames'
  24. const Chatbot = () => {
  25. const {
  26. isMobile,
  27. appInfoError,
  28. appInfoLoading,
  29. appData,
  30. appChatListDataLoading,
  31. chatShouldReloadKey,
  32. handleNewConversation,
  33. themeBuilder,
  34. } = useEmbeddedChatbotContext()
  35. const { t } = useTranslation()
  36. const customConfig = appData?.custom_config
  37. const site = appData?.site
  38. const difyIcon = <LogoHeader />
  39. useEffect(() => {
  40. themeBuilder?.buildTheme(site?.chat_color_theme, site?.chat_color_theme_inverted)
  41. if (site) {
  42. if (customConfig)
  43. document.title = `${site.title}`
  44. else
  45. document.title = `${site.title} - Powered by Dify`
  46. }
  47. }, [site, customConfig, themeBuilder])
  48. if (appInfoLoading) {
  49. return (
  50. <>
  51. {!isMobile && <Loading type='app' />}
  52. {isMobile && (
  53. <div className={cn('relative')}>
  54. <div className={cn('flex flex-col h-[calc(100vh_-_60px)] border-[0.5px] border-components-panel-border rounded-2xl shadow-xs')}>
  55. <Loading type='app' />
  56. </div>
  57. </div>
  58. )}
  59. </>
  60. )
  61. }
  62. if (appInfoError) {
  63. return (
  64. <>
  65. {!isMobile && <AppUnavailable />}
  66. {isMobile && (
  67. <div className={cn('relative')}>
  68. <div className={cn('flex flex-col h-[calc(100vh_-_60px)] border-[0.5px] border-components-panel-border rounded-2xl shadow-xs')}>
  69. <AppUnavailable />
  70. </div>
  71. </div>
  72. )}
  73. </>
  74. )
  75. }
  76. return (
  77. <div className='relative'>
  78. <div
  79. className={cn(
  80. 'flex flex-col border border-components-panel-border-subtle rounded-2xl',
  81. isMobile ? 'h-[calc(100vh_-_60px)] border-[0.5px] border-components-panel-border shadow-xs' : 'h-[100vh] bg-chatbot-bg',
  82. )}
  83. style={isMobile ? Object.assign({}, CssTransform(themeBuilder?.theme?.backgroundHeaderColorStyle ?? '')) : {}}
  84. >
  85. <Header
  86. isMobile={isMobile}
  87. title={site?.title || ''}
  88. customerIcon={isDify() ? difyIcon : ''}
  89. theme={themeBuilder?.theme}
  90. onCreateNewChat={handleNewConversation}
  91. />
  92. <div className={cn('grow flex flex-col overflow-y-auto', isMobile && '!h-[calc(100vh_-_3rem)] bg-chatbot-bg rounded-2xl')}>
  93. {appChatListDataLoading && (
  94. <Loading type='app' />
  95. )}
  96. {!appChatListDataLoading && (
  97. <ChatWrapper key={chatShouldReloadKey} />
  98. )}
  99. </div>
  100. </div>
  101. {/* powered by */}
  102. {isMobile && (
  103. <div className='shrink-0 h-[60px] pl-2 flex items-center'>
  104. {!appData?.custom_config?.remove_webapp_brand && (
  105. <div className={cn(
  106. 'shrink-0 px-2 flex items-center gap-1.5',
  107. )}>
  108. <div className='text-text-tertiary system-2xs-medium-uppercase'>{t('share.chat.poweredBy')}</div>
  109. {appData?.custom_config?.replace_webapp_logo && (
  110. <img src={appData?.custom_config?.replace_webapp_logo} alt='logo' className='block w-auto h-5' />
  111. )}
  112. {!appData?.custom_config?.replace_webapp_logo && (
  113. <LogoSite className='!h-5' />
  114. )}
  115. </div>
  116. )}
  117. </div>
  118. )}
  119. </div>
  120. )
  121. }
  122. const EmbeddedChatbotWrapper = () => {
  123. const media = useBreakpoints()
  124. const isMobile = media === MediaType.mobile
  125. const themeBuilder = useThemeContext()
  126. const {
  127. appInfoError,
  128. appInfoLoading,
  129. appData,
  130. appParams,
  131. appMeta,
  132. appChatListDataLoading,
  133. currentConversationId,
  134. currentConversationItem,
  135. appPrevChatList,
  136. pinnedConversationList,
  137. conversationList,
  138. newConversationInputs,
  139. newConversationInputsRef,
  140. handleNewConversationInputsChange,
  141. inputsForms,
  142. handleNewConversation,
  143. handleStartChat,
  144. handleChangeConversation,
  145. handleNewConversationCompleted,
  146. chatShouldReloadKey,
  147. isInstalledApp,
  148. appId,
  149. handleFeedback,
  150. currentChatInstanceRef,
  151. } = useEmbeddedChatbot()
  152. return <EmbeddedChatbotContext.Provider value={{
  153. appInfoError,
  154. appInfoLoading,
  155. appData,
  156. appParams,
  157. appMeta,
  158. appChatListDataLoading,
  159. currentConversationId,
  160. currentConversationItem,
  161. appPrevChatList,
  162. pinnedConversationList,
  163. conversationList,
  164. newConversationInputs,
  165. newConversationInputsRef,
  166. handleNewConversationInputsChange,
  167. inputsForms,
  168. handleNewConversation,
  169. handleStartChat,
  170. handleChangeConversation,
  171. handleNewConversationCompleted,
  172. chatShouldReloadKey,
  173. isMobile,
  174. isInstalledApp,
  175. appId,
  176. handleFeedback,
  177. currentChatInstanceRef,
  178. themeBuilder,
  179. }}>
  180. <Chatbot />
  181. </EmbeddedChatbotContext.Provider>
  182. }
  183. const EmbeddedChatbot = () => {
  184. const [initialized, setInitialized] = useState(false)
  185. const [appUnavailable, setAppUnavailable] = useState<boolean>(false)
  186. const [isUnknownReason, setIsUnknownReason] = useState<boolean>(false)
  187. useAsyncEffect(async () => {
  188. if (!initialized) {
  189. try {
  190. await checkOrSetAccessToken()
  191. }
  192. catch (e: any) {
  193. if (e.status === 404) {
  194. setAppUnavailable(true)
  195. }
  196. else {
  197. setIsUnknownReason(true)
  198. setAppUnavailable(true)
  199. }
  200. }
  201. setInitialized(true)
  202. }
  203. }, [])
  204. if (!initialized)
  205. return null
  206. if (appUnavailable)
  207. return <AppUnavailable isUnknownReason={isUnknownReason} />
  208. return <EmbeddedChatbotWrapper />
  209. }
  210. export default EmbeddedChatbot