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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import type { FC } from 'react'
  2. import React, { useCallback, useEffect, useState } from 'react'
  3. import { RiCollapseDiagonal2Line, RiExpandDiagonal2Line, RiResetLeftLine } from '@remixicon/react'
  4. import { useTranslation } from 'react-i18next'
  5. import type { Theme } from '../theme/theme-context'
  6. import { CssTransform } from '../theme/utils'
  7. import {
  8. useEmbeddedChatbotContext,
  9. } from '../context'
  10. import Tooltip from '@/app/components/base/tooltip'
  11. import ActionButton from '@/app/components/base/action-button'
  12. import Divider from '@/app/components/base/divider'
  13. import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown'
  14. import DifyLogo from '@/app/components/base/logo/dify-logo'
  15. import cn from '@/utils/classnames'
  16. import { useGlobalPublicStore } from '@/context/global-public-context'
  17. export type IHeaderProps = {
  18. isMobile?: boolean
  19. allowResetChat?: boolean
  20. customerIcon?: React.ReactNode
  21. title: string
  22. theme?: Theme
  23. onCreateNewChat?: () => void
  24. }
  25. const Header: FC<IHeaderProps> = ({
  26. isMobile,
  27. allowResetChat,
  28. customerIcon,
  29. title,
  30. theme,
  31. onCreateNewChat,
  32. }) => {
  33. const { t } = useTranslation()
  34. const {
  35. appData,
  36. currentConversationId,
  37. inputsForms,
  38. } = useEmbeddedChatbotContext()
  39. const isClient = typeof window !== 'undefined'
  40. const isIframe = isClient ? window.self !== window.top : false
  41. const [parentOrigin, setParentOrigin] = useState('')
  42. const [showToggleExpandButton, setShowToggleExpandButton] = useState(false)
  43. const [expanded, setExpanded] = useState(false)
  44. const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
  45. const handleMessageReceived = useCallback((event: MessageEvent) => {
  46. let currentParentOrigin = parentOrigin
  47. if (!currentParentOrigin && event.data.type === 'dify-chatbot-config') {
  48. currentParentOrigin = event.origin
  49. setParentOrigin(event.origin)
  50. }
  51. if (event.origin !== currentParentOrigin)
  52. return
  53. if (event.data.type === 'dify-chatbot-config')
  54. setShowToggleExpandButton(event.data.payload.isToggledByButton && !event.data.payload.isDraggable)
  55. }, [parentOrigin])
  56. useEffect(() => {
  57. if (!isIframe) return
  58. const listener = (event: MessageEvent) => handleMessageReceived(event)
  59. window.addEventListener('message', listener)
  60. window.parent.postMessage({ type: 'dify-chatbot-iframe-ready' }, '*')
  61. return () => window.removeEventListener('message', listener)
  62. }, [isIframe, handleMessageReceived])
  63. const handleToggleExpand = useCallback(() => {
  64. if (!isIframe || !showToggleExpandButton) return
  65. setExpanded(!expanded)
  66. window.parent.postMessage({
  67. type: 'dify-chatbot-expand-change',
  68. }, parentOrigin)
  69. }, [isIframe, parentOrigin, showToggleExpandButton, expanded])
  70. if (!isMobile) {
  71. return (
  72. <div className='flex h-14 shrink-0 items-center justify-end p-3'>
  73. <div className='flex items-center gap-1'>
  74. {/* powered by */}
  75. <div className='shrink-0'>
  76. {!appData?.custom_config?.remove_webapp_brand && (
  77. <div className={cn(
  78. 'flex shrink-0 items-center gap-1.5 px-2',
  79. )}>
  80. <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div>
  81. {
  82. systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
  83. ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' />
  84. : appData?.custom_config?.replace_webapp_logo
  85. ? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' />
  86. : <DifyLogo size='small' />
  87. }
  88. </div>
  89. )}
  90. </div>
  91. {currentConversationId && (
  92. <Divider type='vertical' className='h-3.5' />
  93. )}
  94. {
  95. showToggleExpandButton && (
  96. <Tooltip
  97. popupContent={expanded ? t('share.chat.collapse') : t('share.chat.expand')}
  98. >
  99. <ActionButton size='l' onClick={handleToggleExpand}>
  100. {
  101. expanded
  102. ? <RiCollapseDiagonal2Line className='h-[18px] w-[18px]' />
  103. : <RiExpandDiagonal2Line className='h-[18px] w-[18px]' />
  104. }
  105. </ActionButton>
  106. </Tooltip>
  107. )
  108. }
  109. {currentConversationId && allowResetChat && (
  110. <Tooltip
  111. popupContent={t('share.chat.resetChat')}
  112. >
  113. <ActionButton size='l' onClick={onCreateNewChat}>
  114. <RiResetLeftLine className='h-[18px] w-[18px]' />
  115. </ActionButton>
  116. </Tooltip>
  117. )}
  118. {currentConversationId && inputsForms.length > 0 && (
  119. <ViewFormDropdown />
  120. )}
  121. </div>
  122. </div>
  123. )
  124. }
  125. return (
  126. <div
  127. className={cn('flex h-14 shrink-0 items-center justify-between rounded-t-2xl px-3')}
  128. style={Object.assign({}, CssTransform(theme?.backgroundHeaderColorStyle ?? ''), CssTransform(theme?.headerBorderBottomStyle ?? ''))}
  129. >
  130. <div className="flex grow items-center space-x-3">
  131. {customerIcon}
  132. <div
  133. className='system-md-semibold truncate'
  134. style={CssTransform(theme?.colorFontOnHeaderStyle ?? '')}
  135. >
  136. {title}
  137. </div>
  138. </div>
  139. <div className='flex items-center gap-1'>
  140. {
  141. showToggleExpandButton && (
  142. <Tooltip
  143. popupContent={expanded ? t('share.chat.collapse') : t('share.chat.expand')}
  144. >
  145. <ActionButton size='l' onClick={handleToggleExpand}>
  146. {
  147. expanded
  148. ? <RiCollapseDiagonal2Line className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
  149. : <RiExpandDiagonal2Line className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
  150. }
  151. </ActionButton>
  152. </Tooltip>
  153. )
  154. }
  155. {currentConversationId && allowResetChat && (
  156. <Tooltip
  157. popupContent={t('share.chat.resetChat')}
  158. >
  159. <ActionButton size='l' onClick={onCreateNewChat}>
  160. <RiResetLeftLine className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
  161. </ActionButton>
  162. </Tooltip>
  163. )}
  164. {currentConversationId && inputsForms.length > 0 && (
  165. <ViewFormDropdown iconColor={theme?.colorPathOnHeader} />
  166. )}
  167. </div>
  168. </div>
  169. )
  170. }
  171. export default React.memo(Header)