index.tsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import { useCallback, useMemo, useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import Link from 'next/link'
  4. import { useDebounce } from 'ahooks'
  5. import {
  6. RiAlertFill,
  7. RiArrowDownSLine,
  8. RiArrowRightUpLine,
  9. RiBrainLine,
  10. } from '@remixicon/react'
  11. import SystemModelSelector from './system-model-selector'
  12. import ProviderAddedCard from './provider-added-card'
  13. import type {
  14. ConfigurationMethodEnum,
  15. CustomConfigurationModelFixedFields,
  16. ModelProvider,
  17. } from './declarations'
  18. import {
  19. CustomConfigurationStatusEnum,
  20. ModelTypeEnum,
  21. } from './declarations'
  22. import {
  23. useDefaultModel,
  24. useMarketplaceAllPlugins,
  25. useModelModalHandler,
  26. } from './hooks'
  27. import Divider from '@/app/components/base/divider'
  28. import Loading from '@/app/components/base/loading'
  29. import ProviderCard from '@/app/components/plugins/provider-card'
  30. import List from '@/app/components/plugins/marketplace/list'
  31. import { useProviderContext } from '@/context/provider-context'
  32. import type { Plugin } from '@/app/components/plugins/types'
  33. import { MARKETPLACE_URL_PREFIX } from '@/config'
  34. import cn from '@/utils/classnames'
  35. import { getLocaleOnClient } from '@/i18n'
  36. type Props = {
  37. searchText: string
  38. }
  39. const FixedModelProvider = ['langgenius/openai/openai', 'langgenius/anthropic/anthropic']
  40. const ModelProviderPage = ({ searchText }: Props) => {
  41. const debouncedSearchText = useDebounce(searchText, { wait: 500 })
  42. const { t } = useTranslation()
  43. const { data: textGenerationDefaultModel } = useDefaultModel(ModelTypeEnum.textGeneration)
  44. const { data: embeddingsDefaultModel } = useDefaultModel(ModelTypeEnum.textEmbedding)
  45. const { data: rerankDefaultModel } = useDefaultModel(ModelTypeEnum.rerank)
  46. const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text)
  47. const { data: ttsDefaultModel } = useDefaultModel(ModelTypeEnum.tts)
  48. const { modelProviders: providers } = useProviderContext()
  49. const defaultModelNotConfigured = !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel && !ttsDefaultModel
  50. const [configuredProviders, notConfiguredProviders] = useMemo(() => {
  51. const configuredProviders: ModelProvider[] = []
  52. const notConfiguredProviders: ModelProvider[] = []
  53. providers.forEach((provider) => {
  54. if (
  55. provider.custom_configuration.status === CustomConfigurationStatusEnum.active
  56. || (
  57. provider.system_configuration.enabled === true
  58. && provider.system_configuration.quota_configurations.find(item => item.quota_type === provider.system_configuration.current_quota_type)
  59. )
  60. )
  61. configuredProviders.push(provider)
  62. else
  63. notConfiguredProviders.push(provider)
  64. })
  65. configuredProviders.sort((a, b) => {
  66. if (FixedModelProvider.includes(a.provider) && FixedModelProvider.includes(b.provider))
  67. return FixedModelProvider.indexOf(a.provider) - FixedModelProvider.indexOf(b.provider) > 0 ? 1 : -1
  68. else if (FixedModelProvider.includes(a.provider))
  69. return -1
  70. else if (FixedModelProvider.includes(b.provider))
  71. return 1
  72. return 0
  73. })
  74. return [configuredProviders, notConfiguredProviders]
  75. }, [providers])
  76. const [filteredConfiguredProviders, filteredNotConfiguredProviders] = useMemo(() => {
  77. const filteredConfiguredProviders = configuredProviders.filter(
  78. provider => provider.provider.toLowerCase().includes(debouncedSearchText.toLowerCase())
  79. || Object.values(provider.label).some(text => text.toLowerCase().includes(debouncedSearchText.toLowerCase())),
  80. )
  81. const filteredNotConfiguredProviders = notConfiguredProviders.filter(
  82. provider => provider.provider.toLowerCase().includes(debouncedSearchText.toLowerCase())
  83. || Object.values(provider.label).some(text => text.toLowerCase().includes(debouncedSearchText.toLowerCase())),
  84. )
  85. return [filteredConfiguredProviders, filteredNotConfiguredProviders]
  86. }, [configuredProviders, debouncedSearchText, notConfiguredProviders])
  87. const handleOpenModal = useModelModalHandler()
  88. const [collapse, setCollapse] = useState(false)
  89. const locale = getLocaleOnClient()
  90. const {
  91. plugins: allPlugins,
  92. isLoading: isAllPluginsLoading,
  93. } = useMarketplaceAllPlugins(providers, searchText)
  94. const cardRender = useCallback((plugin: Plugin) => {
  95. if (plugin.type === 'bundle')
  96. return null
  97. return <ProviderCard key={plugin.plugin_id} payload={plugin} />
  98. }, [])
  99. return (
  100. <div className='relative pt-1 -mt-2'>
  101. <div className={cn('flex items-center mb-2')}>
  102. <div className='grow text-text-primary system-md-semibold'>{t('common.modelProvider.models')}</div>
  103. <div className={cn(
  104. 'shrink-0 relative flex items-center justify-end gap-2 p-px rounded-lg border border-transparent',
  105. defaultModelNotConfigured && 'pl-2 bg-components-panel-bg-blur border-components-panel-border shadow-xs',
  106. )}>
  107. {defaultModelNotConfigured && <div className='absolute top-0 bottom-0 right-0 left-0 opacity-40' style={{ background: 'linear-gradient(92deg, rgba(247, 144, 9, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%)' }} />}
  108. {defaultModelNotConfigured && (
  109. <div className='flex items-center gap-1 text-text-primary system-xs-medium'>
  110. <RiAlertFill className='w-4 h-4 text-text-warning-secondary' />
  111. {t('common.modelProvider.notConfigured')}
  112. </div>
  113. )}
  114. <SystemModelSelector
  115. notConfigured={defaultModelNotConfigured}
  116. textGenerationDefaultModel={textGenerationDefaultModel}
  117. embeddingsDefaultModel={embeddingsDefaultModel}
  118. rerankDefaultModel={rerankDefaultModel}
  119. speech2textDefaultModel={speech2textDefaultModel}
  120. ttsDefaultModel={ttsDefaultModel}
  121. />
  122. </div>
  123. </div>
  124. {!filteredConfiguredProviders?.length && (
  125. <div className='mb-2 p-4 rounded-[10px] bg-workflow-process-bg'>
  126. <div className='w-10 h-10 flex items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg backdrop-blur'>
  127. <RiBrainLine className='w-5 h-5 text-text-primary' />
  128. </div>
  129. <div className='mt-2 text-text-secondary system-sm-medium'>{t('common.modelProvider.emptyProviderTitle')}</div>
  130. <div className='mt-1 text-text-tertiary system-xs-regular'>{t('common.modelProvider.emptyProviderTip')}</div>
  131. </div>
  132. )}
  133. {!!filteredConfiguredProviders?.length && (
  134. <div className='relative'>
  135. {filteredConfiguredProviders?.map(provider => (
  136. <ProviderAddedCard
  137. key={provider.provider}
  138. provider={provider}
  139. onOpenModal={(configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields)}
  140. />
  141. ))}
  142. </div>
  143. )}
  144. {!!filteredNotConfiguredProviders?.length && (
  145. <>
  146. <div className='flex items-center mb-2 pt-2 text-text-primary system-md-semibold'>{t('common.modelProvider.toBeConfigured')}</div>
  147. <div className='relative'>
  148. {filteredNotConfiguredProviders?.map(provider => (
  149. <ProviderAddedCard
  150. notConfigured
  151. key={provider.provider}
  152. provider={provider}
  153. onOpenModal={(configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields)}
  154. />
  155. ))}
  156. </div>
  157. </>
  158. )}
  159. <div className='mb-2'>
  160. <Divider className='!mt-4 h-px' />
  161. <div className='flex items-center justify-between'>
  162. <div className='flex items-center gap-1 text-text-primary system-md-semibold cursor-pointer' onClick={() => setCollapse(!collapse)}>
  163. <RiArrowDownSLine className={cn('w-4 h-4', collapse && '-rotate-90')} />
  164. {t('common.modelProvider.installProvider')}
  165. </div>
  166. <div className='flex items-center mb-2 pt-2'>
  167. <span className='pr-1 text-text-tertiary system-sm-regular'>{t('common.modelProvider.discoverMore')}</span>
  168. <Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}`} className='inline-flex items-center system-sm-medium text-text-accent'>
  169. {t('plugin.marketplace.difyMarketplace')}
  170. <RiArrowRightUpLine className='w-4 h-4' />
  171. </Link>
  172. </div>
  173. </div>
  174. {!collapse && isAllPluginsLoading && <Loading type='area' />}
  175. {
  176. !isAllPluginsLoading && !collapse && (
  177. <List
  178. marketplaceCollections={[]}
  179. marketplaceCollectionPluginsMap={{}}
  180. plugins={allPlugins}
  181. showInstallButton
  182. locale={locale}
  183. cardContainerClassName='grid grid-cols-2 gap-2'
  184. cardRender={cardRender}
  185. emptyClassName='h-auto'
  186. />
  187. )
  188. }
  189. </div>
  190. </div>
  191. )
  192. }
  193. export default ModelProviderPage