Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

model-load-balancing-modal.tsx 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import { memo, useCallback, useEffect, useMemo, useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import type {
  4. Credential,
  5. ModelItem,
  6. ModelLoadBalancingConfig,
  7. ModelLoadBalancingConfigEntry,
  8. ModelProvider,
  9. } from '../declarations'
  10. import {
  11. ConfigurationMethodEnum,
  12. FormTypeEnum,
  13. } from '../declarations'
  14. import ModelIcon from '../model-icon'
  15. import ModelName from '../model-name'
  16. import ModelLoadBalancingConfigs from './model-load-balancing-configs'
  17. import classNames from '@/utils/classnames'
  18. import Modal from '@/app/components/base/modal'
  19. import Button from '@/app/components/base/button'
  20. import Loading from '@/app/components/base/loading'
  21. import { useToastContext } from '@/app/components/base/toast'
  22. import { SwitchCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
  23. import {
  24. useGetModelCredential,
  25. useUpdateModelLoadBalancingConfig,
  26. } from '@/service/use-models'
  27. export type ModelLoadBalancingModalProps = {
  28. provider: ModelProvider
  29. configurateMethod: ConfigurationMethodEnum
  30. model: ModelItem
  31. credential?: Credential
  32. open?: boolean
  33. onClose?: () => void
  34. onSave?: (provider: string) => void
  35. }
  36. // model balancing config modal
  37. const ModelLoadBalancingModal = ({
  38. provider,
  39. configurateMethod,
  40. model,
  41. credential,
  42. open = false,
  43. onClose,
  44. onSave,
  45. }: ModelLoadBalancingModalProps) => {
  46. const { t } = useTranslation()
  47. const { notify } = useToastContext()
  48. const [loading, setLoading] = useState(false)
  49. const providerFormSchemaPredefined = configurateMethod === ConfigurationMethodEnum.predefinedModel
  50. const configFrom = providerFormSchemaPredefined ? 'predefined-model' : 'custom-model'
  51. const {
  52. isLoading,
  53. data,
  54. refetch,
  55. } = useGetModelCredential(true, provider.provider, credential?.credential_id, model.model, model.model_type, configFrom)
  56. const modelCredential = data
  57. const {
  58. load_balancing,
  59. current_credential_id,
  60. available_credentials,
  61. current_credential_name,
  62. } = modelCredential ?? {}
  63. const originalConfig = load_balancing
  64. const [draftConfig, setDraftConfig] = useState<ModelLoadBalancingConfig>()
  65. const originalConfigMap = useMemo(() => {
  66. if (!originalConfig)
  67. return {}
  68. return originalConfig?.configs.reduce((prev, config) => {
  69. if (config.id)
  70. prev[config.id] = config
  71. return prev
  72. }, {} as Record<string, ModelLoadBalancingConfigEntry>)
  73. }, [originalConfig])
  74. useEffect(() => {
  75. if (originalConfig)
  76. setDraftConfig(originalConfig)
  77. }, [originalConfig])
  78. const toggleModalBalancing = useCallback((enabled: boolean) => {
  79. if (draftConfig) {
  80. setDraftConfig({
  81. ...draftConfig,
  82. enabled,
  83. })
  84. }
  85. }, [draftConfig])
  86. const extendedSecretFormSchemas = useMemo(
  87. () => {
  88. if (providerFormSchemaPredefined) {
  89. return provider?.provider_credential_schema?.credential_form_schemas?.filter(
  90. ({ type }) => type === FormTypeEnum.secretInput,
  91. ) ?? []
  92. }
  93. return provider?.model_credential_schema?.credential_form_schemas?.filter(
  94. ({ type }) => type === FormTypeEnum.secretInput,
  95. ) ?? []
  96. },
  97. [provider?.model_credential_schema?.credential_form_schemas, provider?.provider_credential_schema?.credential_form_schemas, providerFormSchemaPredefined],
  98. )
  99. const encodeConfigEntrySecretValues = useCallback((entry: ModelLoadBalancingConfigEntry) => {
  100. const result = { ...entry }
  101. extendedSecretFormSchemas.forEach(({ variable }) => {
  102. if (entry.id && result.credentials[variable] === originalConfigMap[entry.id]?.credentials?.[variable])
  103. result.credentials[variable] = '[__HIDDEN__]'
  104. })
  105. return result
  106. }, [extendedSecretFormSchemas, originalConfigMap])
  107. const { mutateAsync: updateModelLoadBalancingConfig } = useUpdateModelLoadBalancingConfig(provider.provider)
  108. const initialCustomModelCredential = useMemo(() => {
  109. if (!current_credential_id)
  110. return undefined
  111. return {
  112. credential_id: current_credential_id,
  113. credential_name: current_credential_name,
  114. }
  115. }, [current_credential_id, current_credential_name])
  116. const [customModelCredential, setCustomModelCredential] = useState<Credential | undefined>(initialCustomModelCredential)
  117. const handleSave = async () => {
  118. try {
  119. setLoading(true)
  120. const res = await updateModelLoadBalancingConfig(
  121. {
  122. credential_id: customModelCredential?.credential_id || current_credential_id,
  123. config_from: configFrom,
  124. model: model.model,
  125. model_type: model.model_type,
  126. load_balancing: {
  127. ...draftConfig,
  128. configs: draftConfig!.configs.map(encodeConfigEntrySecretValues),
  129. enabled: Boolean(draftConfig?.enabled),
  130. },
  131. },
  132. )
  133. if (res.result === 'success') {
  134. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  135. onSave?.(provider.provider)
  136. onClose?.()
  137. }
  138. }
  139. finally {
  140. setLoading(false)
  141. }
  142. }
  143. return (
  144. <Modal
  145. isShow={Boolean(model) && open}
  146. onClose={onClose}
  147. className='w-[640px] max-w-none px-8 pt-8'
  148. title={
  149. <div className='pb-3 font-semibold'>
  150. <div className='h-[30px]'>{
  151. draftConfig?.enabled
  152. ? t('common.modelProvider.auth.configLoadBalancing')
  153. : t('common.modelProvider.auth.configModel')
  154. }</div>
  155. {Boolean(model) && (
  156. <div className='flex h-5 items-center'>
  157. <ModelIcon
  158. className='mr-2 shrink-0'
  159. provider={provider}
  160. modelName={model!.model}
  161. />
  162. <ModelName
  163. className='system-md-regular grow text-text-secondary'
  164. modelItem={model!}
  165. showModelType
  166. showMode
  167. showContextSize
  168. />
  169. </div>
  170. )}
  171. </div>
  172. }
  173. >
  174. {!draftConfig
  175. ? <Loading type='area' />
  176. : (
  177. <>
  178. <div className='py-2'>
  179. <div
  180. className={classNames(
  181. 'min-h-16 rounded-xl border bg-components-panel-bg transition-colors',
  182. draftConfig.enabled ? 'cursor-pointer border-components-panel-border' : 'cursor-default border-util-colors-blue-blue-600',
  183. )}
  184. onClick={draftConfig.enabled ? () => toggleModalBalancing(false) : undefined}
  185. >
  186. <div className='flex select-none items-center gap-2 px-[15px] py-3'>
  187. <div className='flex h-8 w-8 shrink-0 grow-0 items-center justify-center rounded-lg border border-components-card-border bg-components-card-bg'>
  188. {Boolean(model) && (
  189. <ModelIcon className='shrink-0' provider={provider} modelName={model!.model} />
  190. )}
  191. </div>
  192. <div className='grow'>
  193. <div className='text-sm text-text-secondary'>{
  194. providerFormSchemaPredefined
  195. ? t('common.modelProvider.auth.providerManaged')
  196. : t('common.modelProvider.auth.specifyModelCredential')
  197. }</div>
  198. <div className='text-xs text-text-tertiary'>{
  199. providerFormSchemaPredefined
  200. ? t('common.modelProvider.auth.providerManagedTip')
  201. : t('common.modelProvider.auth.specifyModelCredentialTip')
  202. }</div>
  203. </div>
  204. {
  205. !providerFormSchemaPredefined && (
  206. <SwitchCredentialInLoadBalancing
  207. provider={provider}
  208. customModelCredential={initialCustomModelCredential ?? customModelCredential}
  209. setCustomModelCredential={setCustomModelCredential}
  210. model={model}
  211. credentials={available_credentials}
  212. />
  213. )
  214. }
  215. </div>
  216. </div>
  217. {
  218. modelCredential && (
  219. <ModelLoadBalancingConfigs {...{
  220. draftConfig,
  221. setDraftConfig,
  222. provider,
  223. currentCustomConfigurationModelFixedFields: {
  224. __model_name: model.model,
  225. __model_type: model.model_type,
  226. },
  227. configurationMethod: model.fetch_from,
  228. className: 'mt-2',
  229. modelCredential,
  230. onUpdate: refetch,
  231. model: {
  232. model: model.model,
  233. model_type: model.model_type,
  234. },
  235. }} />
  236. )
  237. }
  238. </div>
  239. <div className='mt-6 flex items-center justify-end gap-2'>
  240. <Button onClick={onClose}>{t('common.operation.cancel')}</Button>
  241. <Button
  242. variant='primary'
  243. onClick={handleSave}
  244. disabled={
  245. loading
  246. || (draftConfig?.enabled && (draftConfig?.configs.filter(config => config.enabled).length ?? 0) < 2)
  247. || isLoading
  248. }
  249. >{t('common.operation.save')}</Button>
  250. </div>
  251. </>
  252. )
  253. }
  254. </Modal >
  255. )
  256. }
  257. export default memo(ModelLoadBalancingModal)