index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. 'use client'
  2. import { useTranslation } from 'react-i18next'
  3. import { Fragment, useState } from 'react'
  4. import { useRouter } from 'next/navigation'
  5. import { useContext, useContextSelector } from 'use-context-selector'
  6. import {
  7. RiAccountCircleLine,
  8. RiArrowRightUpLine,
  9. RiBookOpenLine,
  10. RiGithubLine,
  11. RiGraduationCapFill,
  12. RiInformation2Line,
  13. RiLogoutBoxRLine,
  14. RiMap2Line,
  15. RiSettings3Line,
  16. RiStarLine,
  17. } from '@remixicon/react'
  18. import Link from 'next/link'
  19. import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'
  20. import Indicator from '../indicator'
  21. import AccountAbout from '../account-about'
  22. import GithubStar from '../github-star'
  23. import Support from './support'
  24. import Compliance from './compliance'
  25. import PremiumBadge from '@/app/components/base/premium-badge'
  26. import I18n from '@/context/i18n'
  27. import Avatar from '@/app/components/base/avatar'
  28. import { logout } from '@/service/common'
  29. import AppContext, { useAppContext } from '@/context/app-context'
  30. import { useProviderContext } from '@/context/provider-context'
  31. import { useModalContext } from '@/context/modal-context'
  32. import { LanguagesSupported } from '@/i18n/language'
  33. import { LicenseStatus } from '@/types/feature'
  34. import { IS_CLOUD_EDITION } from '@/config'
  35. import cn from '@/utils/classnames'
  36. export default function AppSelector() {
  37. const itemClassName = `
  38. flex items-center w-full h-9 pl-3 pr-2 text-text-secondary system-md-regular
  39. rounded-lg hover:bg-state-base-hover cursor-pointer gap-1
  40. `
  41. const router = useRouter()
  42. const [aboutVisible, setAboutVisible] = useState(false)
  43. const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures)
  44. const { locale } = useContext(I18n)
  45. const { t } = useTranslation()
  46. const { userProfile, langeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext()
  47. const { isEducationAccount } = useProviderContext()
  48. const { setShowAccountSettingModal } = useModalContext()
  49. const handleLogout = async () => {
  50. await logout({
  51. url: '/logout',
  52. params: {},
  53. })
  54. localStorage.removeItem('setup_status')
  55. localStorage.removeItem('console_token')
  56. localStorage.removeItem('refresh_token')
  57. router.push('/signin')
  58. }
  59. return (
  60. <div className="">
  61. <Menu as="div" className="relative inline-block text-left">
  62. {
  63. ({ open }) => (
  64. <>
  65. <MenuButton className={cn('inline-flex items-center rounded-[20px] p-0.5 hover:bg-background-default-dodge', open && 'bg-background-default-dodge')}>
  66. <Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={36} />
  67. </MenuButton>
  68. <Transition
  69. as={Fragment}
  70. enter="transition ease-out duration-100"
  71. enterFrom="transform opacity-0 scale-95"
  72. enterTo="transform opacity-100 scale-100"
  73. leave="transition ease-in duration-75"
  74. leaveFrom="transform opacity-100 scale-100"
  75. leaveTo="transform opacity-0 scale-95"
  76. >
  77. <MenuItems
  78. className="
  79. absolute right-0 mt-1.5 w-60 max-w-80
  80. origin-top-right divide-y divide-divider-subtle rounded-lg bg-components-panel-bg-blur
  81. shadow-lg focus:outline-none
  82. "
  83. >
  84. <MenuItem disabled>
  85. <div className='flex flex-nowrap items-center py-[13px] pl-3 pr-2'>
  86. <div className='grow'>
  87. <div className='system-md-medium break-all text-text-primary'>
  88. {userProfile.name}
  89. {isEducationAccount && (
  90. <PremiumBadge size='s' color='blue' className='ml-1 !px-2'>
  91. <RiGraduationCapFill className='w-3 h-3 mr-1' />
  92. <span className='system-2xs-medium'>EDU</span>
  93. </PremiumBadge>
  94. )}
  95. </div>
  96. <div className='system-xs-regular break-all text-text-tertiary'>{userProfile.email}</div>
  97. </div>
  98. <Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={36} className='mr-3' />
  99. </div>
  100. </MenuItem>
  101. <div className="px-1 py-1">
  102. <MenuItem>
  103. <Link
  104. className={cn(itemClassName, 'group',
  105. 'data-[active]:bg-state-base-hover',
  106. )}
  107. href='/account'
  108. target='_self' rel='noopener noreferrer'>
  109. <RiAccountCircleLine className='size-4 shrink-0 text-text-tertiary' />
  110. <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.account.account')}</div>
  111. <RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' />
  112. </Link>
  113. </MenuItem>
  114. <MenuItem>
  115. <div className={cn(itemClassName,
  116. 'data-[active]:bg-state-base-hover',
  117. )} onClick={() => setShowAccountSettingModal({ payload: 'members' })}>
  118. <RiSettings3Line className='size-4 shrink-0 text-text-tertiary' />
  119. <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.settings')}</div>
  120. </div>
  121. </MenuItem>
  122. </div>
  123. <div className='p-1'>
  124. <MenuItem>
  125. <Link
  126. className={cn(itemClassName, 'group justify-between',
  127. 'data-[active]:bg-state-base-hover',
  128. )}
  129. href={
  130. locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/' : `https://docs.dify.ai/v/${locale.toLowerCase()}/`
  131. }
  132. target='_blank' rel='noopener noreferrer'>
  133. <RiBookOpenLine className='size-4 shrink-0 text-text-tertiary' />
  134. <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.helpCenter')}</div>
  135. <RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' />
  136. </Link>
  137. </MenuItem>
  138. <Support />
  139. {IS_CLOUD_EDITION && isCurrentWorkspaceOwner && <Compliance />}
  140. </div>
  141. <div className='p-1'>
  142. <MenuItem>
  143. <Link
  144. className={cn(itemClassName, 'group justify-between',
  145. 'data-[active]:bg-state-base-hover',
  146. )}
  147. href='https://roadmap.dify.ai'
  148. target='_blank' rel='noopener noreferrer'>
  149. <RiMap2Line className='size-4 shrink-0 text-text-tertiary' />
  150. <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.roadmap')}</div>
  151. <RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' />
  152. </Link>
  153. </MenuItem>
  154. {systemFeatures.license.status === LicenseStatus.NONE && <MenuItem>
  155. <Link
  156. className={cn(itemClassName, 'group justify-between',
  157. 'data-[active]:bg-state-base-hover',
  158. )}
  159. href='https://github.com/langgenius/dify'
  160. target='_blank' rel='noopener noreferrer'>
  161. <RiGithubLine className='size-4 shrink-0 text-text-tertiary' />
  162. <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.github')}</div>
  163. <div className='flex items-center gap-0.5 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-[5px] py-[3px]'>
  164. <RiStarLine className='size-3 shrink-0 text-text-tertiary' />
  165. <GithubStar className='system-2xs-medium-uppercase text-text-tertiary' />
  166. </div>
  167. </Link>
  168. </MenuItem>}
  169. {
  170. document?.body?.getAttribute('data-public-site-about') !== 'hide' && (
  171. <MenuItem>
  172. <div className={cn(itemClassName, 'justify-between',
  173. 'data-[active]:bg-state-base-hover',
  174. )} onClick={() => setAboutVisible(true)}>
  175. <RiInformation2Line className='size-4 shrink-0 text-text-tertiary' />
  176. <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.about')}</div>
  177. <div className='flex shrink-0 items-center'>
  178. <div className='system-xs-regular mr-2 text-text-tertiary'>{langeniusVersionInfo.current_version}</div>
  179. <Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} />
  180. </div>
  181. </div>
  182. </MenuItem>
  183. )
  184. }
  185. </div>
  186. <MenuItem>
  187. <div className='p-1' onClick={() => handleLogout()}>
  188. <div
  189. className={cn(itemClassName, 'group justify-between',
  190. 'data-[active]:bg-state-base-hover',
  191. )}
  192. >
  193. <RiLogoutBoxRLine className='size-4 shrink-0 text-text-tertiary' />
  194. <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.logout')}</div>
  195. </div>
  196. </div>
  197. </MenuItem>
  198. </MenuItems>
  199. </Transition>
  200. </>
  201. )
  202. }
  203. </Menu>
  204. {
  205. aboutVisible && <AccountAbout onCancel={() => setAboutVisible(false)} langeniusVersionInfo={langeniusVersionInfo} />
  206. }
  207. </div >
  208. )
  209. }