model-load-balancing-modal.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import { memo, useCallback, useEffect, useMemo, useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import useSWR from 'swr'
  4. import type { ModelItem, ModelLoadBalancingConfig, ModelLoadBalancingConfigEntry, ModelProvider } from '../declarations'
  5. import { FormTypeEnum } from '../declarations'
  6. import ModelIcon from '../model-icon'
  7. import ModelName from '../model-name'
  8. import { savePredefinedLoadBalancingConfig } from '../utils'
  9. import ModelLoadBalancingConfigs from './model-load-balancing-configs'
  10. import classNames from '@/utils/classnames'
  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. className='max-w-none pt-8 px-8 w-[640px]'
  101. title={
  102. <div className='pb-3 font-semibold'>
  103. <div className='h-[30px]'>{t('common.modelProvider.configLoadBalancing')}</div>
  104. {Boolean(model) && (
  105. <div className='flex items-center h-5'>
  106. <ModelIcon
  107. className='shrink-0 mr-2'
  108. provider={provider}
  109. modelName={model!.model}
  110. />
  111. <ModelName
  112. className='grow text-sm font-normal text-gray-900'
  113. modelItem={model!}
  114. showModelType
  115. showMode
  116. showContextSize
  117. />
  118. </div>
  119. )}
  120. </div>
  121. }
  122. >
  123. {!draftConfig
  124. ? <Loading type='area' />
  125. : (
  126. <>
  127. <div className='py-2'>
  128. <div
  129. className={classNames(
  130. 'min-h-16 bg-gray-50 border rounded-xl transition-colors',
  131. draftConfig.enabled ? 'border-gray-200 cursor-pointer' : 'border-primary-400 cursor-default',
  132. )}
  133. onClick={draftConfig.enabled ? () => toggleModalBalancing(false) : undefined}
  134. >
  135. <div className='flex items-center px-[15px] py-3 gap-2 select-none'>
  136. <div className='grow-0 shrink-0 flex items-center justify-center w-8 h-8 bg-white border rounded-lg'>
  137. {Boolean(model) && (
  138. <ModelIcon className='shrink-0' provider={provider} modelName={model!.model} />
  139. )}
  140. </div>
  141. <div className='grow'>
  142. <div className='text-sm'>{t('common.modelProvider.providerManaged')}</div>
  143. <div className='text-xs text-gray-500'>{t('common.modelProvider.providerManagedDescription')}</div>
  144. </div>
  145. </div>
  146. </div>
  147. <ModelLoadBalancingConfigs {...{
  148. draftConfig,
  149. setDraftConfig,
  150. provider,
  151. currentCustomConfigurationModelFixedFields: {
  152. __model_name: model.model,
  153. __model_type: model.model_type,
  154. },
  155. configurationMethod: model.fetch_from,
  156. className: 'mt-2',
  157. }} />
  158. </div>
  159. <div className='flex items-center justify-end gap-2 mt-6'>
  160. <Button onClick={onClose}>{t('common.operation.cancel')}</Button>
  161. <Button
  162. variant='primary'
  163. onClick={handleSave}
  164. disabled={
  165. loading
  166. || (draftConfig?.enabled && (draftConfig?.configs.filter(config => config.enabled).length ?? 0) < 2)
  167. }
  168. >{t('common.operation.save')}</Button>
  169. </div>
  170. </>
  171. )
  172. }
  173. </Modal >
  174. )
  175. }
  176. export default memo(ModelLoadBalancingModal)