| | |
| | | import { useTranslation } from 'react-i18next' |
| | | import { Fragment, useState } from 'react' |
| | | import { useRouter } from 'next/navigation' |
| | | import { useContextSelector } from 'use-context-selector' |
| | | import { |
| | | RiAccountCircleLine, |
| | | RiArrowRightUpLine, |
| | | RiBookOpenLine, |
| | | RiGithubLine, |
| | | RiGraduationCapFill, |
| | | RiInformation2Line, |
| | | RiLogoutBoxRLine, |
| | | RiMap2Line, |
| | | RiSettings3Line, |
| | | RiStarLine, |
| | | RiTShirt2Line, |
| | | } from '@remixicon/react' |
| | | import { useContext } from 'use-context-selector' |
| | | import { RiArrowDownSLine, RiLogoutBoxRLine } from '@remixicon/react' |
| | | import Link from 'next/link' |
| | | import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' |
| | | import { Menu, Transition } from '@headlessui/react' |
| | | import Indicator from '../indicator' |
| | | import AccountAbout from '../account-about' |
| | | import GithubStar from '../github-star' |
| | | import Support from './support' |
| | | import Compliance from './compliance' |
| | | import PremiumBadge from '@/app/components/base/premium-badge' |
| | | import { useGetDocLanguage } from '@/context/i18n' |
| | | import { mailToSupport } from '../utils/util' |
| | | import WorkplaceSelector from './workplace-selector' |
| | | import classNames from '@/utils/classnames' |
| | | import I18n from '@/context/i18n' |
| | | import Avatar from '@/app/components/base/avatar' |
| | | import ThemeSwitcher from '@/app/components/base/theme-switcher' |
| | | import { logout } from '@/service/common' |
| | | import AppContext, { useAppContext } from '@/context/app-context' |
| | | import { useProviderContext } from '@/context/provider-context' |
| | | import { useAppContext } from '@/context/app-context' |
| | | import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows' |
| | | import { useModalContext } from '@/context/modal-context' |
| | | import { LicenseStatus } from '@/types/feature' |
| | | import { IS_CLOUD_EDITION } from '@/config' |
| | | import cn from '@/utils/classnames' |
| | | import { LanguagesSupported } from '@/i18n/language' |
| | | import { useProviderContext } from '@/context/provider-context' |
| | | import { Plan } from '@/app/components/billing/type' |
| | | |
| | | export default function AppSelector() { |
| | | export type IAppSelector = { |
| | | isMobile: boolean |
| | | } |
| | | |
| | | export default function AppSelector({ isMobile }: IAppSelector) { |
| | | const itemClassName = ` |
| | | flex items-center w-full h-9 pl-3 pr-2 text-text-secondary system-md-regular |
| | | rounded-lg hover:bg-state-base-hover cursor-pointer gap-1 |
| | | flex items-center w-full h-9 px-3 text-text-secondary system-md-regular |
| | | rounded-lg hover:bg-state-base-hover cursor-pointer |
| | | ` |
| | | const router = useRouter() |
| | | const [aboutVisible, setAboutVisible] = useState(false) |
| | | const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures) |
| | | |
| | | const { locale } = useContext(I18n) |
| | | const { t } = useTranslation() |
| | | const { userProfile, langeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext() |
| | | const { isEducationAccount } = useProviderContext() |
| | | const { userProfile, langeniusVersionInfo } = useAppContext() |
| | | const { setShowAccountSettingModal } = useModalContext() |
| | | const docLanguage = useGetDocLanguage() |
| | | const { plan } = useProviderContext() |
| | | const canEmailSupport = plan.type === Plan.professional || plan.type === Plan.team || plan.type === Plan.enterprise |
| | | |
| | | const handleLogout = async () => { |
| | | await logout({ |
| | |
| | | { |
| | | ({ open }) => ( |
| | | <> |
| | | <MenuButton className={cn('inline-flex items-center rounded-[20px] p-0.5 hover:bg-background-default-dodge', open && 'bg-background-default-dodge')}> |
| | | <Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={36} /> |
| | | </MenuButton> |
| | | <Menu.Button |
| | | className={` |
| | | inline-flex items-center |
| | | rounded-[20px] py-1 pr-2.5 pl-1 text-sm |
| | | text-gray-700 hover:bg-gray-200 |
| | | mobile:px-1 |
| | | ${open && 'bg-gray-200'} |
| | | `} |
| | | > |
| | | <Avatar avatar={userProfile.avatar_url} name={userProfile.name} className='sm:mr-2 mr-0' size={32} /> |
| | | {!isMobile && <> |
| | | {userProfile.name} |
| | | <RiArrowDownSLine className="w-3 h-3 ml-1 text-gray-700" /> |
| | | </>} |
| | | </Menu.Button> |
| | | <Transition |
| | | as={Fragment} |
| | | enter="transition ease-out duration-100" |
| | |
| | | leaveFrom="transform opacity-100 scale-100" |
| | | leaveTo="transform opacity-0 scale-95" |
| | | > |
| | | <MenuItems |
| | | <Menu.Items |
| | | className=" |
| | | absolute right-0 mt-1.5 w-60 max-w-80 |
| | | origin-top-right divide-y divide-divider-subtle rounded-xl bg-components-panel-bg-blur shadow-lg |
| | | backdrop-blur-sm focus:outline-none |
| | | divide-y divide-divider-subtle origin-top-right rounded-lg bg-components-panel-bg-blur |
| | | shadow-lg focus:outline-none |
| | | " |
| | | > |
| | | <MenuItem disabled> |
| | | <div className='flex flex-nowrap items-center py-[13px] pl-3 pr-2'> |
| | | <div className='grow'> |
| | | <div className='system-md-medium break-all text-text-primary'> |
| | | {userProfile.name} |
| | | {isEducationAccount && ( |
| | | <PremiumBadge size='s' color='blue' className='ml-1 !px-2'> |
| | | <RiGraduationCapFill className='mr-1 h-3 w-3' /> |
| | | <span className='system-2xs-medium'>EDU</span> |
| | | </PremiumBadge> |
| | | )} |
| | | </div> |
| | | <div className='system-xs-regular break-all text-text-tertiary'>{userProfile.email}</div> |
| | | </div> |
| | | <Menu.Item disabled> |
| | | <div className='flex flex-nowrap items-center px-4 py-[13px]'> |
| | | <Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={36} className='mr-3' /> |
| | | <div className='grow'> |
| | | <div className='system-md-medium text-text-primary break-all'>{userProfile.name}</div> |
| | | <div className='system-xs-regular text-text-tertiary break-all'>{userProfile.email}</div> |
| | | </div> |
| | | </div> |
| | | </MenuItem> |
| | | </Menu.Item> |
| | | <div className='px-1 py-1'> |
| | | <div className='mt-2 px-3 text-xs font-medium text-text-tertiary'>{t('common.userProfile.workspace')}</div> |
| | | <WorkplaceSelector /> |
| | | </div> |
| | | <div className="px-1 py-1"> |
| | | <MenuItem> |
| | | <Link |
| | | className={cn(itemClassName, 'group', |
| | | 'data-[active]:bg-state-base-hover', |
| | | <Menu.Item> |
| | | {({ active }) => <Link |
| | | className={classNames(itemClassName, 'group justify-between', |
| | | active && 'bg-state-base-hover', |
| | | )} |
| | | href='/account' |
| | | target='_self' rel='noopener noreferrer'> |
| | | <RiAccountCircleLine className='size-4 shrink-0 text-text-tertiary' /> |
| | | <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.account.account')}</div> |
| | | <RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' /> |
| | | </Link> |
| | | </MenuItem> |
| | | <MenuItem> |
| | | <div className={cn(itemClassName, |
| | | 'data-[active]:bg-state-base-hover', |
| | | <div>{t('common.account.account')}</div> |
| | | <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' /> |
| | | </Link>} |
| | | </Menu.Item> |
| | | <Menu.Item> |
| | | {({ active }) => <div className={classNames(itemClassName, |
| | | active && 'bg-state-base-hover', |
| | | )} onClick={() => setShowAccountSettingModal({ payload: 'members' })}> |
| | | <RiSettings3Line className='size-4 shrink-0 text-text-tertiary' /> |
| | | <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.settings')}</div> |
| | | </div> |
| | | </MenuItem> |
| | | </div> |
| | | <div className='p-1'> |
| | | <MenuItem> |
| | | <Link |
| | | className={cn(itemClassName, 'group justify-between', |
| | | 'data-[active]:bg-state-base-hover', |
| | | <div>{t('common.userProfile.settings')}</div> |
| | | </div>} |
| | | </Menu.Item> |
| | | {canEmailSupport && <Menu.Item> |
| | | {({ active }) => <a |
| | | className={classNames(itemClassName, 'group justify-between', |
| | | active && 'bg-state-base-hover', |
| | | )} |
| | | href={`https://docs.dify.ai/${docLanguage}/introduction`} |
| | | href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)} |
| | | target='_blank' rel='noopener noreferrer'> |
| | | <RiBookOpenLine className='size-4 shrink-0 text-text-tertiary' /> |
| | | <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.helpCenter')}</div> |
| | | <RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' /> |
| | | </Link> |
| | | </MenuItem> |
| | | <Support /> |
| | | {IS_CLOUD_EDITION && isCurrentWorkspaceOwner && <Compliance />} |
| | | </div> |
| | | <div className='p-1'> |
| | | <MenuItem> |
| | | <Link |
| | | className={cn(itemClassName, 'group justify-between', |
| | | 'data-[active]:bg-state-base-hover', |
| | | <div>{t('common.userProfile.emailSupport')}</div> |
| | | <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' /> |
| | | </a>} |
| | | </Menu.Item>} |
| | | <Menu.Item> |
| | | {({ active }) => <Link |
| | | className={classNames(itemClassName, 'group justify-between', |
| | | active && 'bg-state-base-hover', |
| | | )} |
| | | href='https://github.com/langgenius/dify/discussions/categories/feedbacks' |
| | | target='_blank' rel='noopener noreferrer'> |
| | | <div>{t('common.userProfile.communityFeedback')}</div> |
| | | <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' /> |
| | | </Link>} |
| | | </Menu.Item> |
| | | <Menu.Item> |
| | | {({ active }) => <Link |
| | | className={classNames(itemClassName, 'group justify-between', |
| | | active && 'bg-state-base-hover', |
| | | )} |
| | | href='https://discord.gg/5AEfbxcd9k' |
| | | target='_blank' rel='noopener noreferrer'> |
| | | <div>{t('common.userProfile.community')}</div> |
| | | <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' /> |
| | | </Link>} |
| | | </Menu.Item> |
| | | <Menu.Item> |
| | | {({ active }) => <Link |
| | | className={classNames(itemClassName, 'group justify-between', |
| | | active && 'bg-state-base-hover', |
| | | )} |
| | | href={ |
| | | locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/' : `https://docs.dify.ai/v/${locale.toLowerCase()}/` |
| | | } |
| | | target='_blank' rel='noopener noreferrer'> |
| | | <div>{t('common.userProfile.helpCenter')}</div> |
| | | <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' /> |
| | | </Link>} |
| | | </Menu.Item> |
| | | <Menu.Item> |
| | | {({ active }) => <Link |
| | | className={classNames(itemClassName, 'group justify-between', |
| | | active && 'bg-state-base-hover', |
| | | )} |
| | | href='https://roadmap.dify.ai' |
| | | target='_blank' rel='noopener noreferrer'> |
| | | <RiMap2Line className='size-4 shrink-0 text-text-tertiary' /> |
| | | <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.roadmap')}</div> |
| | | <RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' /> |
| | | </Link> |
| | | </MenuItem> |
| | | {systemFeatures.license.status === LicenseStatus.NONE && <MenuItem> |
| | | <Link |
| | | className={cn(itemClassName, 'group justify-between', |
| | | 'data-[active]:bg-state-base-hover', |
| | | )} |
| | | href='https://github.com/langgenius/dify' |
| | | target='_blank' rel='noopener noreferrer'> |
| | | <RiGithubLine className='size-4 shrink-0 text-text-tertiary' /> |
| | | <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.github')}</div> |
| | | <div className='flex items-center gap-0.5 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-[5px] py-[3px]'> |
| | | <RiStarLine className='size-3 shrink-0 text-text-tertiary' /> |
| | | <GithubStar className='system-2xs-medium-uppercase text-text-tertiary' /> |
| | | </div> |
| | | </Link> |
| | | </MenuItem>} |
| | | <div>{t('common.userProfile.roadmap')}</div> |
| | | <ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' /> |
| | | </Link>} |
| | | </Menu.Item> |
| | | { |
| | | document?.body?.getAttribute('data-public-site-about') !== 'hide' && ( |
| | | <MenuItem> |
| | | <div className={cn(itemClassName, 'justify-between', |
| | | 'data-[active]:bg-state-base-hover', |
| | | <Menu.Item> |
| | | {({ active }) => <div className={classNames(itemClassName, 'justify-between', |
| | | active && 'bg-state-base-hover', |
| | | )} onClick={() => setAboutVisible(true)}> |
| | | <RiInformation2Line className='size-4 shrink-0 text-text-tertiary' /> |
| | | <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.about')}</div> |
| | | <div className='flex shrink-0 items-center'> |
| | | <div className='system-xs-regular mr-2 text-text-tertiary'>{langeniusVersionInfo.current_version}</div> |
| | | <div>{t('common.userProfile.about')}</div> |
| | | <div className='flex items-center'> |
| | | <div className='mr-2 system-xs-regular text-text-tertiary'>{langeniusVersionInfo.current_version}</div> |
| | | <Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} /> |
| | | </div> |
| | | </div> |
| | | </MenuItem> |
| | | </div>} |
| | | </Menu.Item> |
| | | ) |
| | | } |
| | | </div> |
| | | <MenuItem disabled> |
| | | <div className='p-1'> |
| | | <div className={cn(itemClassName, 'hover:bg-transparent')}> |
| | | <RiTShirt2Line className='size-4 shrink-0 text-text-tertiary' /> |
| | | <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.theme.theme')}</div> |
| | | <ThemeSwitcher/> |
| | | </div> |
| | | </div> |
| | | </MenuItem> |
| | | <MenuItem> |
| | | <div className='p-1' onClick={() => handleLogout()}> |
| | | <Menu.Item> |
| | | {({ active }) => <div className='p-1' onClick={() => handleLogout()}> |
| | | <div |
| | | className={cn(itemClassName, 'group justify-between', |
| | | 'data-[active]:bg-state-base-hover', |
| | | )} |
| | | className={ |
| | | classNames('flex items-center justify-between h-9 px-3 rounded-lg cursor-pointer group hover:bg-state-base-hover', |
| | | active && 'bg-state-base-hover')} |
| | | > |
| | | <RiLogoutBoxRLine className='size-4 shrink-0 text-text-tertiary' /> |
| | | <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.logout')}</div> |
| | | <div className='system-md-regular text-text-secondary'>{t('common.userProfile.logout')}</div> |
| | | <RiLogoutBoxRLine className='hidden w-4 h-4 text-text-tertiary group-hover:flex' /> |
| | | </div> |
| | | </div> |
| | | </MenuItem> |
| | | </MenuItems> |
| | | </div>} |
| | | </Menu.Item> |
| | | </Menu.Items> |
| | | </Transition> |
| | | </> |
| | | ) |