index.tsx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import { useState } from 'react'
  2. import cn from 'classnames'
  3. import { useContext } from 'use-context-selector'
  4. import { useTranslation } from 'react-i18next'
  5. import Indicator from '../../../indicator'
  6. import OpenaiProvider from '../openai-provider'
  7. import AzureProvider from '../azure-provider'
  8. import AnthropicProvider from '../anthropic-provider'
  9. import type { ValidatedStatusState } from '../provider-input/useValidateToken'
  10. import { ValidatedStatus } from '../provider-input/useValidateToken'
  11. import s from './index.module.css'
  12. import type { Provider, ProviderAnthropicToken, ProviderAzureToken } from '@/models/common'
  13. import { ProviderName } from '@/models/common'
  14. import { updateProviderAIKey } from '@/service/common'
  15. import { ToastContext } from '@/app/components/base/toast'
  16. import Tooltip from '@/app/components/base/tooltip'
  17. const providerNameMap: Record<string, string> = {
  18. openai: 'OpenAI',
  19. azure_openai: 'Azure OpenAI Service',
  20. }
  21. type IProviderItemProps = {
  22. icon: string
  23. name: string
  24. provider: Provider
  25. activeId: string
  26. onActive: (v: string) => void
  27. onSave: () => void
  28. providedOpenaiProvider?: Provider
  29. }
  30. const ProviderItem = ({
  31. activeId,
  32. icon,
  33. name,
  34. provider,
  35. onActive,
  36. onSave,
  37. providedOpenaiProvider,
  38. }: IProviderItemProps) => {
  39. const { t } = useTranslation()
  40. const [validatedStatus, setValidatedStatus] = useState<ValidatedStatusState>()
  41. const [loading, setLoading] = useState(false)
  42. const { notify } = useContext(ToastContext)
  43. const [token, setToken] = useState<ProviderAzureToken | string | ProviderAnthropicToken>(
  44. provider.provider_name === 'azure_openai'
  45. ? { openai_api_base: '', openai_api_key: '' }
  46. : provider.provider_name === 'anthropic'
  47. ? { anthropic_api_key: '' }
  48. : '',
  49. )
  50. const id = `${provider.provider_name}-${provider.provider_type}`
  51. const isOpen = id === activeId
  52. const comingSoon = false
  53. const isValid = provider.is_valid
  54. const providerTokenHasSetted = () => {
  55. if (provider.provider_name === ProviderName.AZURE_OPENAI) {
  56. return (provider.token && provider.token.openai_api_base && provider.token.openai_api_key)
  57. ? {
  58. openai_api_base: provider.token.openai_api_base,
  59. openai_api_key: provider.token.openai_api_key,
  60. }
  61. : undefined
  62. }
  63. if (provider.provider_name === ProviderName.OPENAI)
  64. return provider.token
  65. if (provider.provider_name === ProviderName.ANTHROPIC)
  66. return provider.token?.anthropic_api_key
  67. }
  68. const handleUpdateToken = async () => {
  69. if (loading)
  70. return
  71. if (validatedStatus?.status === ValidatedStatus.Success) {
  72. try {
  73. setLoading(true)
  74. await updateProviderAIKey({ url: `/workspaces/current/providers/${provider.provider_name}/token`, body: { token } })
  75. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  76. onActive('')
  77. }
  78. catch (e) {
  79. notify({ type: 'error', message: t('common.provider.saveFailed') })
  80. }
  81. finally {
  82. setLoading(false)
  83. onSave()
  84. }
  85. }
  86. }
  87. return (
  88. <div className='mb-2 border-[0.5px] border-gray-200 bg-gray-50 rounded-md'>
  89. <div className='flex items-center px-4 h-[52px] cursor-pointer border-b-[0.5px] border-b-gray-200'>
  90. <div className={cn(s[`icon-${icon}`], 'mr-3 w-6 h-6 rounded-md')} />
  91. <div className='grow text-sm font-medium text-gray-800'>{name}</div>
  92. {
  93. providerTokenHasSetted() && !comingSoon && !isOpen && provider.provider_name !== ProviderName.ANTHROPIC && (
  94. <div className='flex items-center mr-4'>
  95. {!isValid && <div className='text-xs text-[#D92D20]'>{t('common.provider.invalidApiKey')}</div>}
  96. <Indicator color={!isValid ? 'red' : 'green'} className='ml-2' />
  97. </div>
  98. )
  99. }
  100. {
  101. (providerTokenHasSetted() && !comingSoon && !isOpen && provider.provider_name === ProviderName.ANTHROPIC) && (
  102. <div className='flex items-center mr-4'>
  103. {
  104. providedOpenaiProvider?.is_valid
  105. ? !isValid
  106. ? <div className='text-xs text-[#D92D20]'>{t('common.provider.invalidApiKey')}</div>
  107. : null
  108. : <div className='text-xs text-[#DC6803]'>{t('common.provider.anthropic.notEnabled')}</div>
  109. }
  110. <Indicator color={
  111. providedOpenaiProvider?.is_valid
  112. ? isValid
  113. ? 'green'
  114. : 'red'
  115. : 'yellow'
  116. } className='ml-2' />
  117. </div>
  118. )
  119. }
  120. {
  121. !comingSoon && !isOpen && provider.provider_name !== ProviderName.ANTHROPIC && (
  122. <div className='
  123. px-3 h-[28px] bg-white border border-gray-200 rounded-md cursor-pointer
  124. text-xs font-medium text-gray-700 flex items-center
  125. ' onClick={() => onActive(id)}>
  126. {providerTokenHasSetted() ? t('common.provider.editKey') : t('common.provider.addKey')}
  127. </div>
  128. )
  129. }
  130. {
  131. (!comingSoon && !isOpen && provider.provider_name === ProviderName.ANTHROPIC)
  132. ? providedOpenaiProvider?.is_enabled
  133. ? (
  134. <div className='
  135. px-3 h-[28px] bg-white border border-gray-200 rounded-md cursor-pointer
  136. text-xs font-medium text-gray-700 flex items-center
  137. ' onClick={() => providedOpenaiProvider.is_valid && onActive(id)}>
  138. {providerTokenHasSetted() ? t('common.provider.editKey') : t('common.provider.addKey')}
  139. </div>
  140. )
  141. : (
  142. <Tooltip
  143. htmlContent={<div className='w-[320px]'>
  144. {t('common.provider.anthropic.enableTip')}
  145. </div>}
  146. position='bottom'
  147. selector='anthropic-provider-enable-top-tooltip'>
  148. <div className='
  149. px-3 h-[28px] bg-white border border-gray-200 rounded-md cursor-not-allowed
  150. text-xs font-medium text-gray-700 flex items-center opacity-50
  151. '>
  152. {t('common.provider.addKey')}
  153. </div>
  154. </Tooltip>
  155. )
  156. : null
  157. }
  158. {
  159. comingSoon && !isOpen && (
  160. <div className='
  161. flex items-center px-2 h-[22px] border border-[#444CE7] rounded-md
  162. text-xs font-medium text-[#444CE7]
  163. '>
  164. {t('common.provider.comingSoon')}
  165. </div>
  166. )
  167. }
  168. {
  169. isOpen && (
  170. <div className='flex items-center'>
  171. <div className='
  172. flex items-center
  173. mr-[5px] px-3 h-7 rounded-md cursor-pointer
  174. text-xs font-medium text-gray-700
  175. ' onClick={() => onActive('')} >
  176. {t('common.operation.cancel')}
  177. </div>
  178. <div className='
  179. flex items-center
  180. px-3 h-7 rounded-md cursor-pointer bg-primary-700
  181. text-xs font-medium text-white
  182. ' onClick={handleUpdateToken}>
  183. {t('common.operation.save')}
  184. </div>
  185. </div>
  186. )
  187. }
  188. </div>
  189. {
  190. provider.provider_name === ProviderName.OPENAI && isOpen && (
  191. <OpenaiProvider
  192. provider={provider}
  193. onValidatedStatus={v => setValidatedStatus(v)}
  194. onTokenChange={v => setToken(v)}
  195. />
  196. )
  197. }
  198. {
  199. provider.provider_name === ProviderName.AZURE_OPENAI && isOpen && (
  200. <AzureProvider
  201. provider={provider}
  202. onValidatedStatus={v => setValidatedStatus(v)}
  203. onTokenChange={v => setToken(v)}
  204. />
  205. )
  206. }
  207. {
  208. provider.provider_name === ProviderName.ANTHROPIC && isOpen && (
  209. <AnthropicProvider
  210. provider={provider}
  211. onValidatedStatus={v => setValidatedStatus(v)}
  212. onTokenChange={v => setToken(v)}
  213. />
  214. )
  215. }
  216. {
  217. provider.provider_name === ProviderName.ANTHROPIC && !isOpen && providerTokenHasSetted() && providedOpenaiProvider?.is_valid && (
  218. <div className='px-4 py-3 text-[13px] font-medium text-gray-700'>
  219. {t('common.provider.anthropic.using')} {providerNameMap[providedOpenaiProvider.provider_name as string]}
  220. </div>
  221. )
  222. }
  223. {
  224. provider.provider_name === ProviderName.ANTHROPIC && !isOpen && providerTokenHasSetted() && !providedOpenaiProvider?.is_valid && (
  225. <div className='px-4 py-3 bg-[#FFFAEB] text-[13px] font-medium text-gray-700'>
  226. {t('common.provider.anthropic.enableTip')}
  227. </div>
  228. )
  229. }
  230. </div>
  231. )
  232. }
  233. export default ProviderItem