index.tsx 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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 ModelProviderPage = ({ searchText }: Props) => {
  40. const debouncedSearchText = useDebounce(searchText, { wait: 500 })
  41. const { t } = useTranslation()
  42. const { data: textGenerationDefaultModel } = useDefaultModel(ModelTypeEnum.textGeneration)
  43. const { data: embeddingsDefaultModel } = useDefaultModel(ModelTypeEnum.textEmbedding)
  44. const { data: rerankDefaultModel } = useDefaultModel(ModelTypeEnum.rerank)
  45. const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text)
  46. const { data: ttsDefaultModel } = useDefaultModel(ModelTypeEnum.tts)
  47. const { modelProviders: providers } = useProviderContext()
  48. const defaultModelNotConfigured = !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel && !ttsDefaultModel
  49. const [configuredProviders, notConfiguredProviders] = useMemo(() => {
  50. const configuredProviders: ModelProvider[] = []
  51. const notConfiguredProviders: ModelProvider[] = []
  52. providers.forEach((provider) => {
  53. if (
  54. provider.custom_configuration.status === CustomConfigurationStatusEnum.active
  55. || (
  56. provider.system_configuration.enabled === true
  57. && provider.system_configuration.quota_configurations.find(item => item.quota_type === provider.system_configuration.current_quota_type)
  58. )
  59. )
  60. configuredProviders.push(provider)
  61. else
  62. notConfiguredProviders.push(provider)
  63. })
  64. return [configuredProviders, notConfiguredProviders]
  65. }, [providers])
  66. const [filteredConfiguredProviders, filteredNotConfiguredProviders] = useMemo(() => {
  67. const filteredConfiguredProviders = configuredProviders.filter(
  68. provider => provider.provider.toLowerCase().includes(debouncedSearchText.toLowerCase())
  69. || Object.values(provider.label).some(text => text.toLowerCase().includes(debouncedSearchText.toLowerCase())),
  70. )
  71. const filteredNotConfiguredProviders = notConfiguredProviders.filter(
  72. provider => provider.provider.toLowerCase().includes(debouncedSearchText.toLowerCase())
  73. || Object.values(provider.label).some(text => text.toLowerCase().includes(debouncedSearchText.toLowerCase())),
  74. )
  75. return [filteredConfiguredProviders, filteredNotConfiguredProviders]
  76. }, [configuredProviders, debouncedSearchText, notConfiguredProviders])
  77. const handleOpenModal = useModelModalHandler()
  78. const [collapse, setCollapse] = useState(false)
  79. const locale = getLocaleOnClient()
  80. const {
  81. plugins: allPlugins,
  82. isLoading: isAllPluginsLoading,
  83. } = useMarketplaceAllPlugins(providers, searchText)
  84. const cardRender = useCallback((plugin: Plugin) => {
  85. if (plugin.type === 'bundle')
  86. return null
  87. return <ProviderCard key={plugin.plugin_id} payload={plugin} />
  88. }, [])
  89. return (
  90. <div className='relative pt-1 -mt-2'>
  91. <div className={cn('flex items-center mb-2')}>
  92. <div className='grow text-text-primary system-md-semibold'>{t('common.modelProvider.models')}</div>
  93. <div className={cn(
  94. 'shrink-0 relative flex items-center justify-end gap-2 p-px rounded-lg border border-transparent',
  95. defaultModelNotConfigured && 'pl-2 bg-components-panel-bg-blur border-components-panel-border shadow-xs',
  96. )}>
  97. {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%)' }} />}
  98. {defaultModelNotConfigured && (
  99. <div className='flex items-center gap-1 text-text-primary system-xs-medium'>
  100. <RiAlertFill className='w-4 h-4 text-text-warning-secondary' />
  101. {t('common.modelProvider.notConfigured')}
  102. </div>
  103. )}
  104. <SystemModelSelector
  105. notConfigured={defaultModelNotConfigured}
  106. textGenerationDefaultModel={textGenerationDefaultModel}
  107. embeddingsDefaultModel={embeddingsDefaultModel}
  108. rerankDefaultModel={rerankDefaultModel}
  109. speech2textDefaultModel={speech2textDefaultModel}
  110. ttsDefaultModel={ttsDefaultModel}
  111. />
  112. </div>
  113. </div>
  114. {!filteredConfiguredProviders?.length && (
  115. <div className='mb-2 p-4 rounded-[10px] bg-workflow-process-bg'>
  116. <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'>
  117. <RiBrainLine className='w-5 h-5 text-text-primary' />
  118. </div>
  119. <div className='mt-2 text-text-secondary system-sm-medium'>{t('common.modelProvider.emptyProviderTitle')}</div>
  120. <div className='mt-1 text-text-tertiary system-xs-regular'>{t('common.modelProvider.emptyProviderTip')}</div>
  121. </div>
  122. )}
  123. {!!filteredConfiguredProviders?.length && (
  124. <div className='relative'>
  125. {filteredConfiguredProviders?.map(provider => (
  126. <ProviderAddedCard
  127. key={provider.provider}
  128. provider={provider}
  129. onOpenModal={(configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields)}
  130. />
  131. ))}
  132. </div>
  133. )}
  134. {!!filteredNotConfiguredProviders?.length && (
  135. <>
  136. <div className='flex items-center mb-2 pt-2 text-text-primary system-md-semibold'>{t('common.modelProvider.toBeConfigured')}</div>
  137. <div className='relative'>
  138. {filteredNotConfiguredProviders?.map(provider => (
  139. <ProviderAddedCard
  140. notConfigured
  141. key={provider.provider}
  142. provider={provider}
  143. onOpenModal={(configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields)}
  144. />
  145. ))}
  146. </div>
  147. </>
  148. )}
  149. <div className='mb-2'>
  150. <Divider className='!mt-4 h-px' />
  151. <div className='flex items-center justify-between'>
  152. <div className='flex items-center gap-1 text-text-primary system-md-semibold cursor-pointer' onClick={() => setCollapse(!collapse)}>
  153. <RiArrowDownSLine className={cn('w-4 h-4', collapse && '-rotate-90')} />
  154. {t('common.modelProvider.installProvider')}
  155. </div>
  156. <div className='flex items-center mb-2 pt-2'>
  157. <span className='pr-1 text-text-tertiary system-sm-regular'>{t('common.modelProvider.discoverMore')}</span>
  158. <Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}`} className='inline-flex items-center system-sm-medium text-text-accent'>
  159. {t('plugin.marketplace.difyMarketplace')}
  160. <RiArrowRightUpLine className='w-4 h-4' />
  161. </Link>
  162. </div>
  163. </div>
  164. {!collapse && isAllPluginsLoading && <Loading type='area' />}
  165. {
  166. !isAllPluginsLoading && !collapse && (
  167. <List
  168. marketplaceCollections={[]}
  169. marketplaceCollectionPluginsMap={{}}
  170. plugins={allPlugins}
  171. showInstallButton
  172. locale={locale}
  173. cardContainerClassName='grid grid-cols-2 gap-2'
  174. cardRender={cardRender}
  175. emptyClassName='h-auto'
  176. />
  177. )
  178. }
  179. </div>
  180. </div>
  181. )
  182. }
  183. export default ModelProviderPage