model-load-balancing-modal.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import { memo, useCallback, useEffect, useMemo, useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import classNames from 'classnames'
  4. import useSWR from 'swr'
  5. import type { ModelItem, ModelLoadBalancingConfig, ModelLoadBalancingConfigEntry, ModelProvider } from '../declarations'
  6. import { FormTypeEnum } from '../declarations'
  7. import ModelIcon from '../model-icon'
  8. import ModelName from '../model-name'
  9. import { savePredefinedLoadBalancingConfig } from '../utils'
  10. import ModelLoadBalancingConfigs from './model-load-balancing-configs'
  11. import Modal from '@/app/components/base/modal'
  12. import Button from '@/app/components/base/button'
  13. import { fetchModelLoadBalancingConfig } from '@/service/common'
  14. import Loading from '@/app/components/base/loading'
  15. import { useToastContext } from '@/app/components/base/toast'
  16. export type ModelLoadBalancingModalProps = {
  17. provider: ModelProvider
  18. model: ModelItem
  19. open?: boolean
  20. onClose?: () => void
  21. onSave?: (provider: string) => void
  22. }
  23. // model balancing config modal
  24. const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSave }: ModelLoadBalancingModalProps) => {
  25. const { t } = useTranslation()
  26. const { notify } = useToastContext()
  27. const [loading, setLoading] = useState(false)
  28. const { data, mutate } = useSWR(
  29. `/workspaces/current/model-providers/${provider.provider}/models/credentials?model=${model.model}&model_type=${model.model_type}`,
  30. fetchModelLoadBalancingConfig,
  31. )
  32. const originalConfig = data?.load_balancing
  33. const [draftConfig, setDraftConfig] = useState<ModelLoadBalancingConfig>()
  34. const originalConfigMap = useMemo(() => {
  35. if (!originalConfig)
  36. return {}
  37. return originalConfig?.configs.reduce((prev, config) => {
  38. if (config.id)
  39. prev[config.id] = config
  40. return prev
  41. }, {} as Record<string, ModelLoadBalancingConfigEntry>)
  42. }, [originalConfig])
  43. useEffect(() => {
  44. if (originalConfig)
  45. setDraftConfig(originalConfig)
  46. }, [originalConfig])
  47. const toggleModalBalancing = useCallback((enabled: boolean) => {
  48. if (draftConfig) {
  49. setDraftConfig({
  50. ...draftConfig,
  51. enabled,
  52. })
  53. }
  54. }, [draftConfig])
  55. const extendedSecretFormSchemas = useMemo(
  56. () => provider.provider_credential_schema.credential_form_schemas.filter(
  57. ({ type }) => type === FormTypeEnum.secretInput,
  58. ),
  59. [provider.provider_credential_schema.credential_form_schemas],
  60. )
  61. const encodeConfigEntrySecretValues = useCallback((entry: ModelLoadBalancingConfigEntry) => {
  62. const result = { ...entry }
  63. extendedSecretFormSchemas.forEach(({ variable }) => {
  64. if (entry.id && result.credentials[variable] === originalConfigMap[entry.id]?.credentials?.[variable])
  65. result.credentials[variable] = '[__HIDDEN__]'
  66. })
  67. return result
  68. }, [extendedSecretFormSchemas, originalConfigMap])
  69. const handleSave = async () => {
  70. try {
  71. setLoading(true)
  72. const res = await savePredefinedLoadBalancingConfig(
  73. provider.provider,
  74. ({
  75. ...(data?.credentials ?? {}),
  76. __model_type: model.model_type,
  77. __model_name: model.model,
  78. }),
  79. {
  80. ...draftConfig,
  81. enabled: Boolean(draftConfig?.enabled),
  82. configs: draftConfig!.configs.map(encodeConfigEntrySecretValues),
  83. },
  84. )
  85. if (res.result === 'success') {
  86. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  87. mutate()
  88. onSave?.(provider.provider)
  89. onClose?.()
  90. }
  91. }
  92. finally {
  93. setLoading(false)
  94. }
  95. }
  96. return (
  97. <Modal
  98. isShow={Boolean(model) && open}
  99. onClose={onClose}
  100. wrapperClassName='!z-30'
  101. className='max-w-none pt-8 px-8 w-[640px]'
  102. title={
  103. <div className='pb-3 font-semibold'>
  104. <div className='h-[30px]'>{t('common.modelProvider.configLoadBalancing')}</div>
  105. {Boolean(model) && (
  106. <div className='flex items-center h-5'>
  107. <ModelIcon
  108. className='shrink-0 mr-2'
  109. provider={provider}
  110. modelName={model!.model}
  111. />
  112. <ModelName
  113. className='grow text-sm font-normal text-gray-900'
  114. modelItem={model!}
  115. showModelType
  116. showMode
  117. showContextSize
  118. />
  119. </div>
  120. )}
  121. </div>
  122. }
  123. >
  124. {!draftConfig
  125. ? <Loading type='area' />
  126. : (
  127. <>
  128. <div className='py-2'>
  129. <div
  130. className={classNames(
  131. 'min-h-16 bg-gray-50 border rounded-xl transition-colors',
  132. draftConfig.enabled ? 'border-gray-200 cursor-pointer' : 'border-primary-400 cursor-default',
  133. )}
  134. onClick={draftConfig.enabled ? () => toggleModalBalancing(false) : undefined}
  135. >
  136. <div className='flex items-center px-[15px] py-3 gap-2 select-none'>
  137. <div className='grow-0 shrink-0 flex items-center justify-center w-8 h-8 bg-white border rounded-lg'>
  138. {Boolean(model) && (
  139. <ModelIcon className='shrink-0' provider={provider} modelName={model!.model} />
  140. )}
  141. </div>
  142. <div className='grow'>
  143. <div className='text-sm'>{t('common.modelProvider.providerManaged')}</div>
  144. <div className='text-xs text-gray-500'>{t('common.modelProvider.providerManagedDescription')}</div>
  145. </div>
  146. </div>
  147. </div>
  148. <ModelLoadBalancingConfigs {...{
  149. draftConfig,
  150. setDraftConfig,
  151. provider,
  152. currentCustomConfigurationModelFixedFields: {
  153. __model_name: model.model,
  154. __model_type: model.model_type,
  155. },
  156. configurationMethod: model.fetch_from,
  157. className: 'mt-2',
  158. }} />
  159. </div>
  160. <div className='flex items-center justify-end gap-2 mt-6'>
  161. <Button onClick={onClose}>{t('common.operation.cancel')}</Button>
  162. <Button
  163. type='primary'
  164. onClick={handleSave}
  165. disabled={
  166. loading
  167. || (draftConfig?.enabled && (draftConfig?.configs.filter(config => config.enabled).length ?? 0) < 2)
  168. }
  169. >{t('common.operation.save')}</Button>
  170. </div>
  171. </>
  172. )
  173. }
  174. </Modal >
  175. )
  176. }
  177. export default memo(ModelLoadBalancingModal)