index.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import type { FC } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import type {
  4. ModelProvider,
  5. TypeWithI18N,
  6. } from '../declarations'
  7. import { ConfigurateMethodEnum } from '../declarations'
  8. import {
  9. DEFAULT_BACKGROUND_COLOR,
  10. MODEL_PROVIDER_QUOTA_GET_FREE,
  11. modelTypeFormat,
  12. } from '../utils'
  13. import {
  14. useAnthropicBuyQuota,
  15. useFreeQuota,
  16. useLanguage,
  17. useUpdateModelProviders,
  18. } from '../hooks'
  19. import ModelBadge from '../model-badge'
  20. import ProviderIcon from '../provider-icon'
  21. import s from './index.module.css'
  22. import { Plus, Settings01 } from '@/app/components/base/icons/src/vender/line/general'
  23. import { CoinsStacked01 } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
  24. import Button from '@/app/components/base/button'
  25. import { IS_CE_EDITION } from '@/config'
  26. type ProviderCardProps = {
  27. provider: ModelProvider
  28. onOpenModal: (configurateMethod: ConfigurateMethodEnum) => void
  29. }
  30. const TIP_MAP: { [k: string]: TypeWithI18N } = {
  31. minimax: {
  32. en_US: 'Earn 1 million tokens for free',
  33. zh_Hans: '免费获取 100 万个 token',
  34. },
  35. spark: {
  36. en_US: 'Earn 3 million tokens (v3.0) for free',
  37. zh_Hans: '免费获取 300 万个 token (v3.0)',
  38. },
  39. zhipuai: {
  40. en_US: 'Earn 10 million tokens for free',
  41. zh_Hans: '免费获取 1000 万个 token',
  42. },
  43. }
  44. const ProviderCard: FC<ProviderCardProps> = ({
  45. provider,
  46. onOpenModal,
  47. }) => {
  48. const { t } = useTranslation()
  49. const language = useLanguage()
  50. const updateModelProviders = useUpdateModelProviders()
  51. const handlePay = useAnthropicBuyQuota()
  52. const handleFreeQuotaSuccess = () => {
  53. updateModelProviders()
  54. }
  55. const handleFreeQuota = useFreeQuota(handleFreeQuotaSuccess)
  56. const configurateMethods = provider.configurate_methods.filter(method => method !== ConfigurateMethodEnum.fetchFromRemote)
  57. const canGetFreeQuota = MODEL_PROVIDER_QUOTA_GET_FREE.includes(provider.provider) && !IS_CE_EDITION && provider.system_configuration.enabled
  58. return (
  59. <div
  60. className='group relative flex flex-col justify-between px-4 py-3 h-[148px] border-[0.5px] border-black/5 rounded-xl shadow-xs hover:shadow-lg'
  61. style={{ background: provider.background || DEFAULT_BACKGROUND_COLOR }}
  62. >
  63. <div>
  64. <div className='py-0.5'>
  65. <ProviderIcon provider={provider} />
  66. </div>
  67. {
  68. provider.description && (
  69. <div className='mt-1 leading-4 text-xs text-black/[48]'>{provider.description[language] || provider.description.en_US}</div>
  70. )
  71. }
  72. </div>
  73. <div>
  74. <div className={`flex flex-wrap group-hover:hidden gap-0.5 ${canGetFreeQuota && 'pb-[18px]'}`}>
  75. {
  76. provider.supported_model_types.map(modelType => (
  77. <ModelBadge key={modelType}>
  78. {modelTypeFormat(modelType)}
  79. </ModelBadge>
  80. ))
  81. }
  82. {
  83. canGetFreeQuota && (
  84. <div className='absolute left-0 right-0 bottom-0 flex items-center h-[26px] px-4 bg-white/50 rounded-b-xl'>
  85. 📣&nbsp;
  86. <div
  87. className={`${s.vender} text-xs font-medium text-transparent truncate`}
  88. title={TIP_MAP[provider.provider][language]}
  89. >
  90. {TIP_MAP[provider.provider][language]}
  91. </div>
  92. </div>
  93. )
  94. }
  95. </div>
  96. {
  97. canGetFreeQuota && (
  98. <div className='hidden group-hover:block'>
  99. <Button
  100. className='mb-1 w-full h-7 text-xs'
  101. type='primary'
  102. onClick={() => handleFreeQuota(provider.provider)}
  103. >
  104. {t('common.modelProvider.getFreeTokens')}
  105. </Button>
  106. </div>
  107. )
  108. }
  109. <div className={`hidden group-hover:grid grid-cols-${configurateMethods.length} gap-1`}>
  110. {
  111. configurateMethods.map((method) => {
  112. if (method === ConfigurateMethodEnum.predefinedModel) {
  113. return (
  114. <Button
  115. key={method}
  116. className={'h-7 bg-white text-xs text-gray-700 shrink-0'}
  117. onClick={() => onOpenModal(method)}
  118. >
  119. <Settings01 className={`mr-[5px] w-3.5 h-3.5 ${s.icon}`} />
  120. <span className='text-xs inline-flex items-center justify-center overflow-ellipsis shrink-0'>{t('common.operation.setup')}</span>
  121. </Button>
  122. )
  123. }
  124. return (
  125. <Button
  126. key={method}
  127. className='px-0 h-7 bg-white text-xs text-gray-700'
  128. onClick={() => onOpenModal(method)}
  129. >
  130. <Plus className='mr-[5px] w-3.5 h-3.5' />
  131. {t('common.modelProvider.addModel')}
  132. </Button>
  133. )
  134. })
  135. }
  136. {
  137. provider.provider === 'anthropic' && !IS_CE_EDITION && (
  138. <Button
  139. className='h-7 text-xs text-gray-700'
  140. onClick={handlePay}
  141. >
  142. <CoinsStacked01 className='mr-[5px] w-3.5 h-3.5' />
  143. {t('common.modelProvider.buyQuota')}
  144. </Button>
  145. )
  146. }
  147. </div>
  148. </div>
  149. </div>
  150. )
  151. }
  152. export default ProviderCard