index.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import cn from 'classnames'
  6. import Progress from './progress'
  7. import Button from '@/app/components/base/button'
  8. import { LinkExternal02, XClose } from '@/app/components/base/icons/src/vender/line/general'
  9. import AccountSetting from '@/app/components/header/account-setting'
  10. import { IS_CE_EDITION } from '@/config'
  11. import { useProviderContext } from '@/context/provider-context'
  12. import { formatNumber } from '@/utils/format'
  13. const APIKeyInfoPanel: FC = () => {
  14. const isCloud = !IS_CE_EDITION
  15. const { textGenerationModelList } = useProviderContext()
  16. const { t } = useTranslation()
  17. const [showSetAPIKeyModal, setShowSetAPIKeyModal] = useState(false)
  18. const [isShow, setIsShow] = useState(true)
  19. const hasSetAPIKEY = !!textGenerationModelList?.find(({ model_provider: provider }) => {
  20. if (provider.provider_type === 'system' && provider.quota_type === 'paid')
  21. return true
  22. if (provider.provider_type === 'custom')
  23. return true
  24. return false
  25. })
  26. if (hasSetAPIKEY)
  27. return null
  28. // first show in trail and not used exhausted, else find the exhausted
  29. const [used, total, unit, providerName] = (() => {
  30. if (!textGenerationModelList || !isCloud)
  31. return [0, 0, '']
  32. let used = 0
  33. let total = 0
  34. let unit = 'times'
  35. let trailProviderName = ''
  36. let hasFoundNotExhausted = false
  37. textGenerationModelList?.filter(({ model_provider: provider }) => {
  38. return provider.quota_type === 'trial'
  39. }).forEach(({ model_provider: provider }) => {
  40. if (hasFoundNotExhausted)
  41. return
  42. const { provider_name, quota_used, quota_limit, quota_unit } = provider
  43. if (quota_limit !== quota_used)
  44. hasFoundNotExhausted = true
  45. used = quota_used
  46. total = quota_limit
  47. unit = quota_unit
  48. trailProviderName = provider_name
  49. })
  50. return [used, total, unit, trailProviderName]
  51. })()
  52. const usedPercent = Math.round(used / total * 100)
  53. const exhausted = isCloud && usedPercent === 100
  54. if (!(isShow))
  55. return null
  56. return (
  57. <div className={cn(exhausted ? 'bg-[#FEF3F2] border-[#FEE4E2]' : 'bg-[#EFF4FF] border-[#D1E0FF]', 'mb-6 relative rounded-2xl shadow-md border p-8 ')}>
  58. <div className={cn('text-[24px] text-gray-800 font-semibold', isCloud ? 'flex items-center h-8 space-x-1' : 'leading-8 mb-6')}>
  59. {isCloud && <em-emoji id={exhausted ? '🤔' : '😀'} />}
  60. {isCloud
  61. ? (
  62. <div>{t(`appOverview.apiKeyInfo.cloud.${exhausted ? 'exhausted' : 'trial'}.title`, { providerName })}</div>
  63. )
  64. : (
  65. <div>
  66. <div>{t('appOverview.apiKeyInfo.selfHost.title.row1')}</div>
  67. <div>{t('appOverview.apiKeyInfo.selfHost.title.row2')}</div>
  68. </div>
  69. )}
  70. </div>
  71. {isCloud && (
  72. <div className='mt-1 text-sm text-gray-600 font-normal'>{t(`appOverview.apiKeyInfo.cloud.${exhausted ? 'exhausted' : 'trial'}.description`)}</div>
  73. )}
  74. {/* Call times info */}
  75. {isCloud && (
  76. <div className='my-5'>
  77. <div className='flex items-center h-5 space-x-2 text-sm text-gray-700 font-medium'>
  78. <div>{t(`appOverview.apiKeyInfo.${unit === 'times' ? 'callTimes' : 'usedToken'}`)}</div>
  79. <div>·</div>
  80. <div className={cn('font-semibold', exhausted && 'text-[#D92D20]')}>{formatNumber(used)}/{formatNumber(total)}</div>
  81. </div>
  82. <Progress className='mt-2' value={usedPercent} />
  83. </div>
  84. )}
  85. <Button
  86. type='primary'
  87. className='space-x-2'
  88. onClick={() => {
  89. setShowSetAPIKeyModal(true)
  90. }}
  91. >
  92. <div className='text-sm font-medium'>{t('appOverview.apiKeyInfo.setAPIBtn')}</div>
  93. <LinkExternal02 className='w-4 h-4' />
  94. </Button>
  95. {!isCloud && (
  96. <a
  97. className='mt-2 flex items-center h-[26px] text-xs font-medium text-[#155EEF] p-1 space-x-1'
  98. href='https://cloud.dify.ai/apps'
  99. target='_blank'
  100. >
  101. <div>{t('appOverview.apiKeyInfo.tryCloud')}</div>
  102. <LinkExternal02 className='w-3 h-3' />
  103. </a>
  104. )}
  105. <div
  106. onClick={() => setIsShow(false)}
  107. className='absolute right-4 top-4 flex items-center justify-center w-8 h-8 cursor-pointer '>
  108. <XClose className='w-4 h-4 text-gray-500' />
  109. </div>
  110. {
  111. showSetAPIKeyModal && (
  112. <AccountSetting activeTab="provider" onCancel={async () => {
  113. setShowSetAPIKeyModal(false)
  114. }} />
  115. )
  116. }
  117. </div>
  118. )
  119. }
  120. export default React.memo(APIKeyInfoPanel)