popup-item.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import type { FC } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import {
  4. RiFileTextLine,
  5. RiFilmAiLine,
  6. RiImageCircleAiLine,
  7. RiVoiceAiFill,
  8. } from '@remixicon/react'
  9. import type {
  10. DefaultModel,
  11. Model,
  12. ModelItem,
  13. } from '../declarations'
  14. import {
  15. ModelFeatureEnum,
  16. ModelFeatureTextEnum,
  17. ModelTypeEnum,
  18. } from '../declarations'
  19. import {
  20. modelTypeFormat,
  21. sizeFormat,
  22. } from '../utils'
  23. import {
  24. useLanguage,
  25. useUpdateModelList,
  26. useUpdateModelProviders,
  27. } from '../hooks'
  28. import ModelIcon from '../model-icon'
  29. import ModelName from '../model-name'
  30. import ModelBadge from '../model-badge'
  31. import {
  32. ConfigurationMethodEnum,
  33. ModelStatusEnum,
  34. } from '../declarations'
  35. import { Check } from '@/app/components/base/icons/src/vender/line/general'
  36. import { useModalContext } from '@/context/modal-context'
  37. import { useProviderContext } from '@/context/provider-context'
  38. import Tooltip from '@/app/components/base/tooltip'
  39. import cn from '@/utils/classnames'
  40. type PopupItemProps = {
  41. defaultModel?: DefaultModel
  42. model: Model
  43. onSelect: (provider: string, model: ModelItem) => void
  44. }
  45. const PopupItem: FC<PopupItemProps> = ({
  46. defaultModel,
  47. model,
  48. onSelect,
  49. }) => {
  50. const { t } = useTranslation()
  51. const language = useLanguage()
  52. const { setShowModelModal } = useModalContext()
  53. const { modelProviders } = useProviderContext()
  54. const updateModelList = useUpdateModelList()
  55. const updateModelProviders = useUpdateModelProviders()
  56. const currentProvider = modelProviders.find(provider => provider.provider === model.provider)!
  57. const handleSelect = (provider: string, modelItem: ModelItem) => {
  58. if (modelItem.status !== ModelStatusEnum.active)
  59. return
  60. onSelect(provider, modelItem)
  61. }
  62. const handleOpenModelModal = () => {
  63. setShowModelModal({
  64. payload: {
  65. currentProvider,
  66. currentConfigurationMethod: ConfigurationMethodEnum.predefinedModel,
  67. },
  68. onSaveCallback: () => {
  69. updateModelProviders()
  70. const modelType = model.models[0].model_type
  71. if (modelType)
  72. updateModelList(modelType)
  73. },
  74. })
  75. }
  76. return (
  77. <div className='mb-1'>
  78. <div className='flex items-center px-3 h-[22px] text-xs font-medium text-text-tertiary'>
  79. {model.label[language] || model.label.en_US}
  80. </div>
  81. {
  82. model.models.map(modelItem => (
  83. <Tooltip
  84. key={modelItem.model}
  85. position='right'
  86. popupClassName='p-3 !w-[206px] bg-components-panel-bg-blur backdrop-blur-sm border-[0.5px] border-components-panel-border rounded-xl'
  87. popupContent={
  88. <div className='flex flex-col gap-1'>
  89. <div className='flex flex-col items-start gap-2'>
  90. <ModelIcon
  91. className={cn('shrink-0 w-5 h-5')}
  92. provider={model}
  93. modelName={modelItem.model}
  94. />
  95. <div className='text-text-primary system-md-medium text-wrap break-words'>{modelItem.label[language] || modelItem.label.en_US}</div>
  96. </div>
  97. {/* {currentProvider?.description && (
  98. <div className='text-text-tertiary system-xs-regular'>{currentProvider?.description?.[language] || currentProvider?.description?.en_US}</div>
  99. )} */}
  100. <div className='flex flex-wrap gap-1'>
  101. {modelItem.model_type && (
  102. <ModelBadge>
  103. {modelTypeFormat(modelItem.model_type)}
  104. </ModelBadge>
  105. )}
  106. {modelItem.model_properties.mode && (
  107. <ModelBadge>
  108. {(modelItem.model_properties.mode as string).toLocaleUpperCase()}
  109. </ModelBadge>
  110. )}
  111. {modelItem.model_properties.context_size && (
  112. <ModelBadge>
  113. {sizeFormat(modelItem.model_properties.context_size as number)}
  114. </ModelBadge>
  115. )}
  116. </div>
  117. {modelItem.model_type === ModelTypeEnum.textGeneration && modelItem.features?.some(feature => [ModelFeatureEnum.vision, ModelFeatureEnum.audio, ModelFeatureEnum.video, ModelFeatureEnum.document].includes(feature)) && (
  118. <div className='pt-2'>
  119. <div className='mb-1 text-text-tertiary system-2xs-medium-uppercase'>{t('common.model.capabilities')}</div>
  120. <div className='flex flex-wrap gap-1'>
  121. {modelItem.features?.includes(ModelFeatureEnum.vision) && (
  122. <ModelBadge>
  123. <RiImageCircleAiLine className='w-3.5 h-3.5 mr-0.5' />
  124. <span>{ModelFeatureTextEnum.vision}</span>
  125. </ModelBadge>
  126. )}
  127. {modelItem.features?.includes(ModelFeatureEnum.audio) && (
  128. <ModelBadge>
  129. <RiVoiceAiFill className='w-3.5 h-3.5 mr-0.5' />
  130. <span>{ModelFeatureTextEnum.audio}</span>
  131. </ModelBadge>
  132. )}
  133. {modelItem.features?.includes(ModelFeatureEnum.video) && (
  134. <ModelBadge>
  135. <RiFilmAiLine className='w-3.5 h-3.5 mr-0.5' />
  136. <span>{ModelFeatureTextEnum.video}</span>
  137. </ModelBadge>
  138. )}
  139. {modelItem.features?.includes(ModelFeatureEnum.document) && (
  140. <ModelBadge>
  141. <RiFileTextLine className='w-3.5 h-3.5 mr-0.5' />
  142. <span>{ModelFeatureTextEnum.document}</span>
  143. </ModelBadge>
  144. )}
  145. </div>
  146. </div>
  147. )}
  148. </div>
  149. }
  150. >
  151. <div
  152. key={modelItem.model}
  153. className={cn('group relative flex items-center px-3 py-1.5 h-8 rounded-lg gap-1', modelItem.status === ModelStatusEnum.active ? 'cursor-pointer hover:bg-state-base-hover' : 'cursor-not-allowed hover:bg-state-base-hover-alt')}
  154. onClick={() => handleSelect(model.provider, modelItem)}
  155. >
  156. <div className='flex items-center gap-2'>
  157. <ModelIcon
  158. className={cn('shrink-0 w-5 h-5')}
  159. provider={model}
  160. modelName={modelItem.model}
  161. />
  162. <ModelName
  163. className={cn('text-text-secondary system-sm-medium', modelItem.status !== ModelStatusEnum.active && 'opacity-60')}
  164. modelItem={modelItem}
  165. />
  166. </div>
  167. {
  168. defaultModel?.model === modelItem.model && defaultModel.provider === currentProvider.provider && (
  169. <Check className='shrink-0 w-4 h-4 text-text-accent' />
  170. )
  171. }
  172. {
  173. modelItem.status === ModelStatusEnum.noConfigure && (
  174. <div
  175. className='hidden group-hover:block text-xs font-medium text-text-accent cursor-pointer'
  176. onClick={handleOpenModelModal}
  177. >
  178. {t('common.operation.add').toLocaleUpperCase()}
  179. </div>
  180. )
  181. }
  182. </div>
  183. </Tooltip>
  184. ))
  185. }
  186. </div>
  187. )
  188. }
  189. export default PopupItem