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.

index.tsx 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. 'use client'
  2. import { useTranslation } from 'react-i18next'
  3. import { Fragment, useCallback } from 'react'
  4. import {
  5. RiAddLine,
  6. RiArrowDownSLine,
  7. RiArrowRightSLine,
  8. } from '@remixicon/react'
  9. import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'
  10. import { useRouter } from 'next/navigation'
  11. import { debounce } from 'lodash-es'
  12. import cn from '@/utils/classnames'
  13. import AppIcon from '@/app/components/base/app-icon'
  14. import { AiText, BubbleTextMod, ChatBot, CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication'
  15. import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
  16. import { useAppContext } from '@/context/app-context'
  17. import { useStore as useAppStore } from '@/app/components/app/store'
  18. import { FileArrow01, FilePlus01, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files'
  19. import type { AppIconType } from '@/types/app'
  20. export type NavItem = {
  21. id: string
  22. name: string
  23. link: string
  24. icon_type: AppIconType | null
  25. icon: string
  26. icon_background: string
  27. icon_url: string | null
  28. mode?: string
  29. }
  30. export type INavSelectorProps = {
  31. navs: NavItem[]
  32. curNav?: Omit<NavItem, 'link'>
  33. createText: string
  34. isApp?: boolean
  35. onCreate: (state: string) => void
  36. onLoadmore?: () => void
  37. }
  38. const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }: INavSelectorProps) => {
  39. const { t } = useTranslation()
  40. const router = useRouter()
  41. const { isCurrentWorkspaceEditor } = useAppContext()
  42. const setAppDetail = useAppStore(state => state.setAppDetail)
  43. const handleScroll = useCallback(debounce((e) => {
  44. if (typeof onLoadmore === 'function') {
  45. const { clientHeight, scrollHeight, scrollTop } = e.target
  46. if (clientHeight + scrollTop > scrollHeight - 50)
  47. onLoadmore()
  48. }
  49. }, 50), [])
  50. return (
  51. <Menu as="div" className="relative">
  52. {({ open }) => (
  53. <>
  54. <MenuButton className={cn(
  55. 'hover:hover:bg-components-main-nav-nav-button-bg-active-hover group inline-flex h-7 w-full items-center justify-center rounded-[10px] pl-2 pr-2.5 text-[14px] font-semibold text-components-main-nav-nav-button-text-active',
  56. open && 'bg-components-main-nav-nav-button-bg-active',
  57. )}>
  58. <div className='max-w-[157px] truncate' title={curNav?.name}>{curNav?.name}</div>
  59. <RiArrowDownSLine
  60. className={cn('ml-1 h-3 w-3 shrink-0 opacity-50 group-hover:opacity-100', open && '!opacity-100')}
  61. aria-hidden="true"
  62. />
  63. </MenuButton>
  64. <MenuItems
  65. className="
  66. absolute -left-11 right-0 mt-1.5 w-60 max-w-80
  67. origin-top-right divide-y divide-divider-regular rounded-lg bg-components-panel-bg-blur
  68. shadow-lg
  69. "
  70. >
  71. <div className="overflow-auto px-1 py-1" style={{ maxHeight: '50vh' }} onScroll={handleScroll}>
  72. {
  73. navs.map(nav => (
  74. <MenuItem key={nav.id}>
  75. <div className='flex w-full cursor-pointer items-center truncate rounded-lg px-3 py-[6px] text-[14px] font-normal text-text-secondary hover:bg-state-base-hover' onClick={() => {
  76. if (curNav?.id === nav.id)
  77. return
  78. setAppDetail()
  79. router.push(nav.link)
  80. }} title={nav.name}>
  81. <div className='relative mr-2 h-6 w-6 rounded-md'>
  82. <AppIcon size='tiny' iconType={nav.icon_type} icon={nav.icon} background={nav.icon_background} imageUrl={nav.icon_url} />
  83. {!!nav.mode && (
  84. <span className={cn(
  85. 'absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 rounded border-[0.5px] border-[rgba(0,0,0,0.02)] bg-white p-0.5 shadow-sm',
  86. )}>
  87. {nav.mode === 'advanced-chat' && (
  88. <BubbleTextMod className='h-2.5 w-2.5 text-[#1570EF]' />
  89. )}
  90. {nav.mode === 'agent-chat' && (
  91. <CuteRobot className='h-2.5 w-2.5 text-indigo-600' />
  92. )}
  93. {nav.mode === 'chat' && (
  94. <ChatBot className='h-2.5 w-2.5 text-[#1570EF]' />
  95. )}
  96. {nav.mode === 'completion' && (
  97. <AiText className='h-2.5 w-2.5 text-[#0E9384]' />
  98. )}
  99. {nav.mode === 'workflow' && (
  100. <Route className='h-2.5 w-2.5 text-[#f79009]' />
  101. )}
  102. </span>
  103. )}
  104. </div>
  105. <div className='truncate'>
  106. {nav.name}
  107. </div>
  108. </div>
  109. </MenuItem>
  110. ))
  111. }
  112. </div>
  113. {!isApp && isCurrentWorkspaceEditor && (
  114. <MenuItem as="div" className='w-full p-1'>
  115. <div onClick={() => onCreate('')} className={cn(
  116. 'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover ',
  117. )}>
  118. <div className='flex h-6 w-6 shrink-0 items-center justify-center rounded-[6px] border-[0.5px] border-divider-regular bg-background-default'>
  119. <RiAddLine className='h-4 w-4 text-text-primary' />
  120. </div>
  121. <div className='grow text-left text-[14px] font-normal text-text-secondary'>{createText}</div>
  122. </div>
  123. </MenuItem>
  124. )}
  125. {isApp && isCurrentWorkspaceEditor && (
  126. <Menu as="div" className="relative h-full w-full">
  127. {({ open }) => (
  128. <>
  129. <MenuButton className='w-full p-1'>
  130. <div className={cn(
  131. 'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover',
  132. open && '!bg-state-base-hover',
  133. )}>
  134. <div className='flex h-6 w-6 shrink-0 items-center justify-center rounded-[6px] border-[0.5px] border-divider-regular bg-background-default'>
  135. <RiAddLine className='h-4 w-4 text-text-primary' />
  136. </div>
  137. <div className='grow text-left text-[14px] font-normal text-text-secondary'>{createText}</div>
  138. <RiArrowRightSLine className='h-3.5 w-3.5 shrink-0 text-text-primary' />
  139. </div>
  140. </MenuButton>
  141. <Transition
  142. as={Fragment}
  143. enter="transition ease-out duration-100"
  144. enterFrom="transform opacity-0 scale-95"
  145. enterTo="transform opacity-100 scale-100"
  146. leave="transition ease-in duration-75"
  147. leaveFrom="transform opacity-100 scale-100"
  148. leaveTo="transform opacity-0 scale-95"
  149. >
  150. <MenuItems className={cn(
  151. 'absolute right-[-198px] top-[3px] z-10 min-w-[200px] rounded-lg bg-components-panel-bg-blur shadow-lg',
  152. )}>
  153. <div className='p-1'>
  154. <div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('blank')}>
  155. <FilePlus01 className='mr-2 h-4 w-4 shrink-0 text-text-secondary' />
  156. {t('app.newApp.startFromBlank')}
  157. </div>
  158. <div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('template')}>
  159. <FilePlus02 className='mr-2 h-4 w-4 shrink-0 text-text-secondary' />
  160. {t('app.newApp.startFromTemplate')}
  161. </div>
  162. </div>
  163. <div className='border-t border-divider-regular p-1'>
  164. <div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('dsl')}>
  165. <FileArrow01 className='mr-2 h-4 w-4 shrink-0 text-text-secondary' />
  166. {t('app.importDSL')}
  167. </div>
  168. </div>
  169. </MenuItems>
  170. </Transition>
  171. </>
  172. )}
  173. </Menu>
  174. )}
  175. </MenuItems>
  176. </>
  177. )}
  178. </Menu>
  179. )
  180. }
  181. export default NavSelector