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.

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