You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

provider-context.tsx 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. 'use client'
  2. import { createContext, useContext, useContextSelector } from 'use-context-selector'
  3. import useSWR from 'swr'
  4. import { useEffect, useState } from 'react'
  5. import dayjs from 'dayjs'
  6. import { useTranslation } from 'react-i18next'
  7. import {
  8. fetchModelList,
  9. fetchModelProviders,
  10. fetchSupportRetrievalMethods,
  11. } from '@/service/common'
  12. import {
  13. CurrentSystemQuotaTypeEnum,
  14. ModelStatusEnum,
  15. ModelTypeEnum,
  16. } from '@/app/components/header/account-setting/model-provider-page/declarations'
  17. import type { Model, ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations'
  18. import type { RETRIEVE_METHOD } from '@/types/app'
  19. import type { BasicPlan } from '@/app/components/billing/type'
  20. import { Plan, type UsagePlanInfo } from '@/app/components/billing/type'
  21. import { fetchCurrentPlanInfo } from '@/service/billing'
  22. import { parseCurrentPlan } from '@/app/components/billing/utils'
  23. import { defaultPlan } from '@/app/components/billing/config'
  24. import Toast from '@/app/components/base/toast'
  25. import {
  26. useEducationStatus,
  27. } from '@/service/use-education'
  28. import { noop } from 'lodash-es'
  29. type ProviderContextState = {
  30. modelProviders: ModelProvider[]
  31. refreshModelProviders: () => void
  32. textGenerationModelList: Model[]
  33. supportRetrievalMethods: RETRIEVE_METHOD[]
  34. isAPIKeySet: boolean
  35. plan: {
  36. type: BasicPlan
  37. usage: UsagePlanInfo
  38. total: UsagePlanInfo
  39. }
  40. isFetchedPlan: boolean
  41. enableBilling: boolean
  42. onPlanInfoChanged: () => void
  43. enableReplaceWebAppLogo: boolean
  44. modelLoadBalancingEnabled: boolean
  45. datasetOperatorEnabled: boolean
  46. enableEducationPlan: boolean
  47. isEducationWorkspace: boolean
  48. isEducationAccount: boolean
  49. webappCopyrightEnabled: boolean
  50. licenseLimit: {
  51. workspace_members: {
  52. size: number
  53. limit: number
  54. }
  55. },
  56. refreshLicenseLimit: () => void
  57. }
  58. const ProviderContext = createContext<ProviderContextState>({
  59. modelProviders: [],
  60. refreshModelProviders: noop,
  61. textGenerationModelList: [],
  62. supportRetrievalMethods: [],
  63. isAPIKeySet: true,
  64. plan: {
  65. type: Plan.sandbox,
  66. usage: {
  67. vectorSpace: 32,
  68. buildApps: 12,
  69. teamMembers: 1,
  70. annotatedResponse: 1,
  71. documentsUploadQuota: 50,
  72. },
  73. total: {
  74. vectorSpace: 200,
  75. buildApps: 50,
  76. teamMembers: 1,
  77. annotatedResponse: 10,
  78. documentsUploadQuota: 500,
  79. },
  80. },
  81. isFetchedPlan: false,
  82. enableBilling: false,
  83. onPlanInfoChanged: noop,
  84. enableReplaceWebAppLogo: false,
  85. modelLoadBalancingEnabled: false,
  86. datasetOperatorEnabled: false,
  87. enableEducationPlan: false,
  88. isEducationWorkspace: false,
  89. isEducationAccount: false,
  90. webappCopyrightEnabled: false,
  91. licenseLimit: {
  92. workspace_members: {
  93. size: 0,
  94. limit: 0,
  95. },
  96. },
  97. refreshLicenseLimit: noop,
  98. })
  99. export const useProviderContext = () => useContext(ProviderContext)
  100. // Adding a dangling comma to avoid the generic parsing issue in tsx, see:
  101. // https://github.com/microsoft/TypeScript/issues/15713
  102. export const useProviderContextSelector = <T,>(selector: (state: ProviderContextState) => T): T =>
  103. useContextSelector(ProviderContext, selector)
  104. type ProviderContextProviderProps = {
  105. children: React.ReactNode
  106. }
  107. export const ProviderContextProvider = ({
  108. children,
  109. }: ProviderContextProviderProps) => {
  110. const { data: providersData, mutate: refreshModelProviders } = useSWR('/workspaces/current/model-providers', fetchModelProviders)
  111. const fetchModelListUrlPrefix = '/workspaces/current/models/model-types/'
  112. const { data: textGenerationModelList } = useSWR(`${fetchModelListUrlPrefix}${ModelTypeEnum.textGeneration}`, fetchModelList)
  113. const { data: supportRetrievalMethods } = useSWR('/datasets/retrieval-setting', fetchSupportRetrievalMethods)
  114. const [plan, setPlan] = useState(defaultPlan)
  115. const [isFetchedPlan, setIsFetchedPlan] = useState(false)
  116. const [enableBilling, setEnableBilling] = useState(true)
  117. const [enableReplaceWebAppLogo, setEnableReplaceWebAppLogo] = useState(false)
  118. const [modelLoadBalancingEnabled, setModelLoadBalancingEnabled] = useState(false)
  119. const [datasetOperatorEnabled, setDatasetOperatorEnabled] = useState(false)
  120. const [webappCopyrightEnabled, setWebappCopyrightEnabled] = useState(false)
  121. const [licenseLimit, setLicenseLimit] = useState({
  122. workspace_members: {
  123. size: 0,
  124. limit: 0,
  125. },
  126. })
  127. const [enableEducationPlan, setEnableEducationPlan] = useState(false)
  128. const [isEducationWorkspace, setIsEducationWorkspace] = useState(false)
  129. const { data: isEducationAccount } = useEducationStatus(!enableEducationPlan)
  130. const fetchPlan = async () => {
  131. try {
  132. const data = await fetchCurrentPlanInfo()
  133. if (!data) {
  134. console.error('Failed to fetch plan info: data is undefined')
  135. return
  136. }
  137. // set default value to avoid undefined error
  138. setEnableBilling(data.billing?.enabled ?? false)
  139. setEnableEducationPlan(data.education?.enabled ?? false)
  140. setIsEducationWorkspace(data.education?.activated ?? false)
  141. setEnableReplaceWebAppLogo(data.can_replace_logo ?? false)
  142. if (data.billing?.enabled) {
  143. setPlan(parseCurrentPlan(data) as any)
  144. setIsFetchedPlan(true)
  145. }
  146. if (data.model_load_balancing_enabled)
  147. setModelLoadBalancingEnabled(true)
  148. if (data.dataset_operator_enabled)
  149. setDatasetOperatorEnabled(true)
  150. if (data.model_load_balancing_enabled)
  151. setModelLoadBalancingEnabled(true)
  152. if (data.dataset_operator_enabled)
  153. setDatasetOperatorEnabled(true)
  154. if (data.webapp_copyright_enabled)
  155. setWebappCopyrightEnabled(true)
  156. if (data.workspace_members)
  157. setLicenseLimit({ workspace_members: data.workspace_members })
  158. }
  159. catch (error) {
  160. console.error('Failed to fetch plan info:', error)
  161. // set default value to avoid undefined error
  162. setEnableBilling(false)
  163. setEnableEducationPlan(false)
  164. setIsEducationWorkspace(false)
  165. setEnableReplaceWebAppLogo(false)
  166. }
  167. }
  168. useEffect(() => {
  169. fetchPlan()
  170. }, [])
  171. const { t } = useTranslation()
  172. useEffect(() => {
  173. if (localStorage.getItem('anthropic_quota_notice') === 'true')
  174. return
  175. if (dayjs().isAfter(dayjs('2025-03-17')))
  176. return
  177. if (providersData?.data && providersData.data.length > 0) {
  178. const anthropic = providersData.data.find(provider => provider.provider === 'anthropic')
  179. if (anthropic && anthropic.system_configuration.current_quota_type === CurrentSystemQuotaTypeEnum.trial) {
  180. const quota = anthropic.system_configuration.quota_configurations.find(item => item.quota_type === anthropic.system_configuration.current_quota_type)
  181. if (quota && quota.is_valid && quota.quota_used < quota.quota_limit) {
  182. Toast.notify({
  183. type: 'info',
  184. message: t('common.provider.anthropicHosted.trialQuotaTip'),
  185. duration: 60000,
  186. onClose: () => {
  187. localStorage.setItem('anthropic_quota_notice', 'true')
  188. },
  189. })
  190. }
  191. }
  192. }
  193. }, [providersData, t])
  194. return (
  195. <ProviderContext.Provider value={{
  196. modelProviders: providersData?.data || [],
  197. refreshModelProviders,
  198. textGenerationModelList: textGenerationModelList?.data || [],
  199. isAPIKeySet: !!textGenerationModelList?.data.some(model => model.status === ModelStatusEnum.active),
  200. supportRetrievalMethods: supportRetrievalMethods?.retrieval_method || [],
  201. plan,
  202. isFetchedPlan,
  203. enableBilling,
  204. onPlanInfoChanged: fetchPlan,
  205. enableReplaceWebAppLogo,
  206. modelLoadBalancingEnabled,
  207. datasetOperatorEnabled,
  208. enableEducationPlan,
  209. isEducationWorkspace,
  210. isEducationAccount: isEducationAccount?.result || false,
  211. webappCopyrightEnabled,
  212. licenseLimit,
  213. refreshLicenseLimit: fetchPlan,
  214. }}>
  215. {children}
  216. </ProviderContext.Provider>
  217. )
  218. }
  219. export default ProviderContext