index.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import {
  2. memo,
  3. useCallback,
  4. useMemo,
  5. useState,
  6. } from 'react'
  7. import {
  8. RiAddLine,
  9. RiEqualizer2Line,
  10. } from '@remixicon/react'
  11. import { useTranslation } from 'react-i18next'
  12. import {
  13. PortalToFollowElem,
  14. PortalToFollowElemContent,
  15. PortalToFollowElemTrigger,
  16. } from '@/app/components/base/portal-to-follow-elem'
  17. import type {
  18. PortalToFollowElemOptions,
  19. } from '@/app/components/base/portal-to-follow-elem'
  20. import Button from '@/app/components/base/button'
  21. import cn from '@/utils/classnames'
  22. import Confirm from '@/app/components/base/confirm'
  23. import type {
  24. ConfigurationMethodEnum,
  25. Credential,
  26. CustomConfigurationModelFixedFields,
  27. CustomModel,
  28. ModelProvider,
  29. } from '../../declarations'
  30. import { useAuth } from '../hooks'
  31. import AuthorizedItem from './authorized-item'
  32. type AuthorizedProps = {
  33. provider: ModelProvider,
  34. configurationMethod: ConfigurationMethodEnum,
  35. currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields,
  36. isModelCredential?: boolean
  37. items: {
  38. title?: string
  39. model?: CustomModel
  40. credentials: Credential[]
  41. }[]
  42. selectedCredential?: Credential
  43. disabled?: boolean
  44. renderTrigger?: (open?: boolean) => React.ReactNode
  45. isOpen?: boolean
  46. onOpenChange?: (open: boolean) => void
  47. offset?: PortalToFollowElemOptions['offset']
  48. placement?: PortalToFollowElemOptions['placement']
  49. triggerPopupSameWidth?: boolean
  50. popupClassName?: string
  51. showItemSelectedIcon?: boolean
  52. onUpdate?: () => void
  53. onItemClick?: (credential: Credential, model?: CustomModel) => void
  54. enableAddModelCredential?: boolean
  55. bottomAddModelCredentialText?: string
  56. }
  57. const Authorized = ({
  58. provider,
  59. configurationMethod,
  60. currentCustomConfigurationModelFixedFields,
  61. items,
  62. isModelCredential,
  63. selectedCredential,
  64. disabled,
  65. renderTrigger,
  66. isOpen,
  67. onOpenChange,
  68. offset = 8,
  69. placement = 'bottom-end',
  70. triggerPopupSameWidth = false,
  71. popupClassName,
  72. showItemSelectedIcon,
  73. onUpdate,
  74. onItemClick,
  75. enableAddModelCredential,
  76. bottomAddModelCredentialText,
  77. }: AuthorizedProps) => {
  78. const { t } = useTranslation()
  79. const [isLocalOpen, setIsLocalOpen] = useState(false)
  80. const mergedIsOpen = isOpen ?? isLocalOpen
  81. const setMergedIsOpen = useCallback((open: boolean) => {
  82. if (onOpenChange)
  83. onOpenChange(open)
  84. setIsLocalOpen(open)
  85. }, [onOpenChange])
  86. const {
  87. openConfirmDelete,
  88. closeConfirmDelete,
  89. doingAction,
  90. handleActiveCredential,
  91. handleConfirmDelete,
  92. deleteCredentialId,
  93. handleOpenModal,
  94. } = useAuth(provider, configurationMethod, currentCustomConfigurationModelFixedFields, isModelCredential, onUpdate)
  95. const handleEdit = useCallback((credential?: Credential, model?: CustomModel) => {
  96. handleOpenModal(credential, model)
  97. setMergedIsOpen(false)
  98. }, [handleOpenModal, setMergedIsOpen])
  99. const handleItemClick = useCallback((credential: Credential, model?: CustomModel) => {
  100. if (onItemClick)
  101. onItemClick(credential, model)
  102. else
  103. handleActiveCredential(credential, model)
  104. setMergedIsOpen(false)
  105. }, [handleActiveCredential, onItemClick, setMergedIsOpen])
  106. const notAllowCustomCredential = provider.allow_custom_token === false
  107. const Trigger = useMemo(() => {
  108. const Item = (
  109. <Button
  110. className='grow'
  111. size='small'
  112. >
  113. <RiEqualizer2Line className='mr-1 h-3.5 w-3.5' />
  114. {t('common.operation.config')}
  115. </Button>
  116. )
  117. return Item
  118. }, [t])
  119. return (
  120. <>
  121. <PortalToFollowElem
  122. open={mergedIsOpen}
  123. onOpenChange={setMergedIsOpen}
  124. placement={placement}
  125. offset={offset}
  126. triggerPopupSameWidth={triggerPopupSameWidth}
  127. >
  128. <PortalToFollowElemTrigger
  129. onClick={() => {
  130. setMergedIsOpen(!mergedIsOpen)
  131. }}
  132. asChild
  133. >
  134. {
  135. renderTrigger
  136. ? renderTrigger(mergedIsOpen)
  137. : Trigger
  138. }
  139. </PortalToFollowElemTrigger>
  140. <PortalToFollowElemContent className='z-[100]'>
  141. <div className={cn(
  142. 'w-[360px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg',
  143. popupClassName,
  144. )}>
  145. <div className='max-h-[304px] overflow-y-auto'>
  146. {
  147. items.map((item, index) => (
  148. <AuthorizedItem
  149. key={index}
  150. title={item.title}
  151. model={item.model}
  152. credentials={item.credentials}
  153. disabled={disabled}
  154. onDelete={openConfirmDelete}
  155. onEdit={handleEdit}
  156. showItemSelectedIcon={showItemSelectedIcon}
  157. selectedCredentialId={selectedCredential?.credential_id}
  158. onItemClick={handleItemClick}
  159. enableAddModelCredential={enableAddModelCredential}
  160. notAllowCustomCredential={notAllowCustomCredential}
  161. />
  162. ))
  163. }
  164. </div>
  165. <div className='h-[1px] bg-divider-subtle'></div>
  166. {
  167. isModelCredential && !notAllowCustomCredential && (
  168. <div
  169. onClick={() => handleEdit(
  170. undefined,
  171. currentCustomConfigurationModelFixedFields
  172. ? {
  173. model: currentCustomConfigurationModelFixedFields.__model_name,
  174. model_type: currentCustomConfigurationModelFixedFields.__model_type,
  175. }
  176. : undefined,
  177. )}
  178. className='system-xs-medium flex h-[30px] cursor-pointer items-center px-3 text-text-accent-light-mode-only'
  179. >
  180. <RiAddLine className='mr-1 h-4 w-4' />
  181. {bottomAddModelCredentialText ?? t('common.modelProvider.auth.addModelCredential')}
  182. </div>
  183. )
  184. }
  185. {
  186. !isModelCredential && !notAllowCustomCredential && (
  187. <div className='p-2'>
  188. <Button
  189. onClick={() => handleEdit()}
  190. className='w-full'
  191. >
  192. {t('common.modelProvider.auth.addApiKey')}
  193. </Button>
  194. </div>
  195. )
  196. }
  197. </div>
  198. </PortalToFollowElemContent>
  199. </PortalToFollowElem>
  200. {
  201. deleteCredentialId && (
  202. <Confirm
  203. isShow
  204. title={t('common.modelProvider.confirmDelete')}
  205. isDisabled={doingAction}
  206. onCancel={closeConfirmDelete}
  207. onConfirm={handleConfirmDelete}
  208. />
  209. )
  210. }
  211. </>
  212. )
  213. }
  214. export default memo(Authorized)