您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

index.tsx 6.3KB

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