use-plugins.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. import { useCallback } from 'react'
  2. import type {
  3. ModelProvider,
  4. } from '@/app/components/header/account-setting/model-provider-page/declarations'
  5. import { fetchModelProviderModelList } from '@/service/common'
  6. import { fetchPluginInfoFromMarketPlace } from '@/service/plugins'
  7. import type {
  8. DebugInfo as DebugInfoTypes,
  9. Dependency,
  10. GitHubItemAndMarketPlaceDependency,
  11. InstallPackageResponse,
  12. InstalledPluginListResponse,
  13. PackageDependency,
  14. Permissions,
  15. Plugin,
  16. PluginDetail,
  17. PluginInfoFromMarketPlace,
  18. PluginTask,
  19. PluginType,
  20. PluginsFromMarketplaceByInfoResponse,
  21. PluginsFromMarketplaceResponse,
  22. VersionInfo,
  23. VersionListResponse,
  24. uploadGitHubResponse,
  25. } from '@/app/components/plugins/types'
  26. import { TaskStatus } from '@/app/components/plugins/types'
  27. import { PluginType as PluginTypeEnum } from '@/app/components/plugins/types'
  28. import type {
  29. PluginsSearchParams,
  30. } from '@/app/components/plugins/marketplace/types'
  31. import { get, getMarketplace, post, postMarketplace } from './base'
  32. import type { MutateOptions, QueryOptions } from '@tanstack/react-query'
  33. import {
  34. useMutation,
  35. useQuery,
  36. useQueryClient,
  37. } from '@tanstack/react-query'
  38. import { useInvalidateAllBuiltInTools } from './use-tools'
  39. import usePermission from '@/app/components/plugins/plugin-page/use-permission'
  40. import { uninstallPlugin } from '@/service/plugins'
  41. import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
  42. const NAME_SPACE = 'plugins'
  43. const useInstalledPluginListKey = [NAME_SPACE, 'installedPluginList']
  44. export const useCheckInstalled = ({
  45. pluginIds,
  46. enabled,
  47. }: {
  48. pluginIds: string[],
  49. enabled: boolean
  50. }) => {
  51. return useQuery<{ plugins: PluginDetail[] }>({
  52. queryKey: [NAME_SPACE, 'checkInstalled', pluginIds],
  53. queryFn: () => post<{ plugins: PluginDetail[] }>('/workspaces/current/plugin/list/installations/ids', {
  54. body: {
  55. plugin_ids: pluginIds,
  56. },
  57. }),
  58. enabled,
  59. staleTime: 0, // always fresh
  60. })
  61. }
  62. export const useInstalledPluginList = (disable?: boolean) => {
  63. return useQuery<InstalledPluginListResponse>({
  64. queryKey: useInstalledPluginListKey,
  65. queryFn: () => get<InstalledPluginListResponse>('/workspaces/current/plugin/list'),
  66. enabled: !disable,
  67. initialData: !disable ? undefined : { plugins: [] },
  68. })
  69. }
  70. export const useInvalidateInstalledPluginList = () => {
  71. const queryClient = useQueryClient()
  72. const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools()
  73. return () => {
  74. queryClient.invalidateQueries(
  75. {
  76. queryKey: useInstalledPluginListKey,
  77. })
  78. invalidateAllBuiltInTools()
  79. }
  80. }
  81. export const useInstallPackageFromMarketPlace = (options?: MutateOptions<InstallPackageResponse, Error, string>) => {
  82. return useMutation({
  83. ...options,
  84. mutationFn: (uniqueIdentifier: string) => {
  85. return post<InstallPackageResponse>('/workspaces/current/plugin/install/marketplace', { body: { plugin_unique_identifiers: [uniqueIdentifier] } })
  86. },
  87. })
  88. }
  89. export const useUpdatePackageFromMarketPlace = (options?: MutateOptions<InstallPackageResponse, Error, object>) => {
  90. return useMutation({
  91. ...options,
  92. mutationFn: (body: object) => {
  93. return post<InstallPackageResponse>('/workspaces/current/plugin/upgrade/marketplace', {
  94. body,
  95. })
  96. },
  97. })
  98. }
  99. export const useVersionListOfPlugin = (pluginID: string) => {
  100. return useQuery<{ data: VersionListResponse }>({
  101. enabled: !!pluginID,
  102. queryKey: [NAME_SPACE, 'versions', pluginID],
  103. queryFn: () => getMarketplace<{ data: VersionListResponse }>(`/plugins/${pluginID}/versions`, { params: { page: 1, page_size: 100 } }),
  104. })
  105. }
  106. export const useInvalidateVersionListOfPlugin = () => {
  107. const queryClient = useQueryClient()
  108. return (pluginID: string) => {
  109. queryClient.invalidateQueries({ queryKey: [NAME_SPACE, 'versions', pluginID] })
  110. }
  111. }
  112. export const useInstallPackageFromLocal = () => {
  113. return useMutation({
  114. mutationFn: (uniqueIdentifier: string) => {
  115. return post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
  116. body: { plugin_unique_identifiers: [uniqueIdentifier] },
  117. })
  118. },
  119. })
  120. }
  121. export const useInstallPackageFromGitHub = () => {
  122. return useMutation({
  123. mutationFn: ({ repoUrl, selectedVersion, selectedPackage, uniqueIdentifier }: {
  124. repoUrl: string
  125. selectedVersion: string
  126. selectedPackage: string
  127. uniqueIdentifier: string
  128. }) => {
  129. return post<InstallPackageResponse>('/workspaces/current/plugin/install/github', {
  130. body: {
  131. repo: repoUrl,
  132. version: selectedVersion,
  133. package: selectedPackage,
  134. plugin_unique_identifier: uniqueIdentifier,
  135. },
  136. })
  137. },
  138. })
  139. }
  140. export const useUploadGitHub = (payload: {
  141. repo: string
  142. version: string
  143. package: string
  144. }) => {
  145. return useQuery({
  146. queryKey: [NAME_SPACE, 'uploadGitHub', payload],
  147. queryFn: () => post<uploadGitHubResponse>('/workspaces/current/plugin/upload/github', {
  148. body: payload,
  149. }),
  150. retry: 0,
  151. })
  152. }
  153. export const useInstallOrUpdate = ({
  154. onSuccess,
  155. }: {
  156. onSuccess?: (res: { success: boolean }[]) => void
  157. }) => {
  158. const { mutateAsync: updatePackageFromMarketPlace } = useUpdatePackageFromMarketPlace()
  159. return useMutation({
  160. mutationFn: (data: {
  161. payload: Dependency[],
  162. plugin: Plugin[],
  163. installedInfo: Record<string, VersionInfo>
  164. }) => {
  165. const { payload, plugin, installedInfo } = data
  166. return Promise.all(payload.map(async (item, i) => {
  167. try {
  168. const orgAndName = `${plugin[i]?.org || plugin[i]?.author}/${plugin[i]?.name}`
  169. const installedPayload = installedInfo[orgAndName]
  170. const isInstalled = !!installedPayload
  171. let uniqueIdentifier = ''
  172. if (item.type === 'github') {
  173. const data = item as GitHubItemAndMarketPlaceDependency
  174. // From local bundle don't have data.value.github_plugin_unique_identifier
  175. if (!data.value.github_plugin_unique_identifier) {
  176. const { unique_identifier } = await post<uploadGitHubResponse>('/workspaces/current/plugin/upload/github', {
  177. body: {
  178. repo: data.value.repo!,
  179. version: data.value.release! || data.value.version!,
  180. package: data.value.packages! || data.value.package!,
  181. },
  182. })
  183. uniqueIdentifier = data.value.github_plugin_unique_identifier! || unique_identifier
  184. // has the same version, but not installed
  185. if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {
  186. return {
  187. success: true,
  188. }
  189. }
  190. }
  191. if (!isInstalled) {
  192. await post<InstallPackageResponse>('/workspaces/current/plugin/install/github', {
  193. body: {
  194. repo: data.value.repo!,
  195. version: data.value.release! || data.value.version!,
  196. package: data.value.packages! || data.value.package!,
  197. plugin_unique_identifier: uniqueIdentifier,
  198. },
  199. })
  200. }
  201. }
  202. if (item.type === 'marketplace') {
  203. const data = item as GitHubItemAndMarketPlaceDependency
  204. uniqueIdentifier = data.value.plugin_unique_identifier! || plugin[i]?.plugin_id
  205. if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {
  206. return {
  207. success: true,
  208. }
  209. }
  210. if (!isInstalled) {
  211. await post<InstallPackageResponse>('/workspaces/current/plugin/install/marketplace', {
  212. body: {
  213. plugin_unique_identifiers: [uniqueIdentifier],
  214. },
  215. })
  216. }
  217. }
  218. if (item.type === 'package') {
  219. const data = item as PackageDependency
  220. uniqueIdentifier = data.value.unique_identifier
  221. if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {
  222. return {
  223. success: true,
  224. }
  225. }
  226. if (!isInstalled) {
  227. await post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
  228. body: {
  229. plugin_unique_identifiers: [uniqueIdentifier],
  230. },
  231. })
  232. }
  233. }
  234. if (isInstalled) {
  235. if (item.type === 'package') {
  236. await uninstallPlugin(installedPayload.installedId)
  237. await post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
  238. body: {
  239. plugin_unique_identifiers: [uniqueIdentifier],
  240. },
  241. })
  242. }
  243. else {
  244. await updatePackageFromMarketPlace({
  245. original_plugin_unique_identifier: installedPayload?.uniqueIdentifier,
  246. new_plugin_unique_identifier: uniqueIdentifier,
  247. })
  248. }
  249. }
  250. return ({ success: true })
  251. }
  252. // eslint-disable-next-line unused-imports/no-unused-vars
  253. catch (e) {
  254. return Promise.resolve({ success: false })
  255. }
  256. }))
  257. },
  258. onSuccess,
  259. })
  260. }
  261. export const useDebugKey = () => {
  262. return useQuery({
  263. queryKey: [NAME_SPACE, 'debugKey'],
  264. queryFn: () => get<DebugInfoTypes>('/workspaces/current/plugin/debugging-key'),
  265. })
  266. }
  267. const usePermissionsKey = [NAME_SPACE, 'permissions']
  268. export const usePermissions = () => {
  269. return useQuery({
  270. queryKey: usePermissionsKey,
  271. queryFn: () => get<Permissions>('/workspaces/current/plugin/permission/fetch'),
  272. })
  273. }
  274. export const useInvalidatePermissions = () => {
  275. const queryClient = useQueryClient()
  276. return () => {
  277. queryClient.invalidateQueries(
  278. {
  279. queryKey: usePermissionsKey,
  280. })
  281. }
  282. }
  283. export const useMutationPermissions = ({
  284. onSuccess,
  285. }: {
  286. onSuccess?: () => void
  287. }) => {
  288. return useMutation({
  289. mutationFn: (payload: Permissions) => {
  290. return post('/workspaces/current/plugin/permission/change', { body: payload })
  291. },
  292. onSuccess,
  293. })
  294. }
  295. export const useMutationPluginsFromMarketplace = () => {
  296. return useMutation({
  297. mutationFn: (pluginsSearchParams: PluginsSearchParams) => {
  298. const {
  299. query,
  300. sortBy,
  301. sortOrder,
  302. category,
  303. tags,
  304. exclude,
  305. type,
  306. page = 1,
  307. pageSize = 40,
  308. } = pluginsSearchParams
  309. const pluginOrBundle = type === 'bundle' ? 'bundles' : 'plugins'
  310. return postMarketplace<{ data: PluginsFromMarketplaceResponse }>(`/${pluginOrBundle}/search/basic`, {
  311. body: {
  312. page,
  313. page_size: pageSize,
  314. query,
  315. sort_by: sortBy,
  316. sort_order: sortOrder,
  317. category: category !== 'all' ? category : '',
  318. tags,
  319. exclude,
  320. type,
  321. },
  322. })
  323. },
  324. })
  325. }
  326. export const useFetchPluginsInMarketPlaceByIds = (unique_identifiers: string[], options?: QueryOptions<{ data: PluginsFromMarketplaceResponse }>) => {
  327. return useQuery({
  328. ...options,
  329. queryKey: [NAME_SPACE, 'fetchPluginsInMarketPlaceByIds', unique_identifiers],
  330. queryFn: () => postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/identifier/batch', {
  331. body: {
  332. unique_identifiers,
  333. },
  334. }),
  335. enabled: unique_identifiers?.filter(i => !!i).length > 0,
  336. retry: 0,
  337. })
  338. }
  339. export const useFetchPluginsInMarketPlaceByInfo = (infos: Record<string, any>[]) => {
  340. return useQuery({
  341. queryKey: [NAME_SPACE, 'fetchPluginsInMarketPlaceByInfo', infos],
  342. queryFn: () => postMarketplace<{ data: PluginsFromMarketplaceByInfoResponse }>('/plugins/versions/batch', {
  343. body: {
  344. plugin_tuples: infos.map(info => ({
  345. org: info.organization,
  346. name: info.plugin,
  347. version: info.version,
  348. })),
  349. },
  350. }),
  351. enabled: infos?.filter(i => !!i).length > 0,
  352. retry: 0,
  353. })
  354. }
  355. const usePluginTaskListKey = [NAME_SPACE, 'pluginTaskList']
  356. export const usePluginTaskList = (category?: PluginType) => {
  357. const {
  358. canManagement,
  359. } = usePermission()
  360. const { refreshPluginList } = useRefreshPluginList()
  361. const {
  362. data,
  363. isFetched,
  364. refetch,
  365. ...rest
  366. } = useQuery({
  367. enabled: canManagement,
  368. queryKey: usePluginTaskListKey,
  369. queryFn: () => get<{ tasks: PluginTask[] }>('/workspaces/current/plugin/tasks?page=1&page_size=100'),
  370. refetchInterval: (lastQuery) => {
  371. const lastData = lastQuery.state.data
  372. const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed)
  373. const taskAllFailed = lastData?.tasks.every(task => task.status === TaskStatus.failed)
  374. if (taskDone) {
  375. if (lastData?.tasks.length && !taskAllFailed)
  376. refreshPluginList(category ? { category } as any : undefined, !category)
  377. return false
  378. }
  379. return 5000
  380. },
  381. })
  382. const handleRefetch = useCallback(() => {
  383. refetch()
  384. }, [refetch])
  385. return {
  386. data,
  387. pluginTasks: data?.tasks || [],
  388. isFetched,
  389. handleRefetch,
  390. ...rest,
  391. }
  392. }
  393. export const useMutationClearTaskPlugin = () => {
  394. return useMutation({
  395. mutationFn: ({ taskId, pluginId }: { taskId: string; pluginId: string }) => {
  396. return post<{ success: boolean }>(`/workspaces/current/plugin/tasks/${taskId}/delete/${pluginId}`)
  397. },
  398. })
  399. }
  400. export const useMutationClearAllTaskPlugin = () => {
  401. return useMutation({
  402. mutationFn: () => {
  403. return post<{ success: boolean }>('/workspaces/current/plugin/tasks/delete_all')
  404. },
  405. })
  406. }
  407. export const usePluginManifestInfo = (pluginUID: string) => {
  408. return useQuery({
  409. enabled: !!pluginUID,
  410. queryKey: [[NAME_SPACE, 'manifest', pluginUID]],
  411. queryFn: () => getMarketplace<{ data: { plugin: PluginInfoFromMarketPlace, version: { version: string } } }>(`/plugins/${pluginUID}`),
  412. retry: 0,
  413. })
  414. }
  415. export const useDownloadPlugin = (info: { organization: string; pluginName: string; version: string }, needDownload: boolean) => {
  416. return useQuery({
  417. queryKey: [NAME_SPACE, 'downloadPlugin', info],
  418. queryFn: () => getMarketplace<Blob>(`/plugins/${info.organization}/${info.pluginName}/${info.version}/download`),
  419. enabled: needDownload,
  420. retry: 0,
  421. })
  422. }
  423. export const useMutationCheckDependencies = () => {
  424. return useMutation({
  425. mutationFn: (appId: string) => {
  426. return get<{ leaked_dependencies: Dependency[] }>(`/apps/imports/${appId}/check-dependencies`)
  427. },
  428. })
  429. }
  430. export const useModelInList = (currentProvider?: ModelProvider, modelId?: string) => {
  431. return useQuery({
  432. queryKey: ['modelInList', currentProvider?.provider, modelId],
  433. queryFn: async () => {
  434. if (!modelId || !currentProvider) return false
  435. try {
  436. const modelsData = await fetchModelProviderModelList(`/workspaces/current/model-providers/${currentProvider?.provider}/models`)
  437. return !!modelId && !!modelsData.data.find(item => item.model === modelId)
  438. }
  439. catch (error) {
  440. return false
  441. }
  442. },
  443. enabled: !!modelId && !!currentProvider,
  444. })
  445. }
  446. export const usePluginInfo = (providerName?: string) => {
  447. return useQuery({
  448. queryKey: ['pluginInfo', providerName],
  449. queryFn: async () => {
  450. if (!providerName) return null
  451. const parts = providerName.split('/')
  452. const org = parts[0]
  453. const name = parts[1]
  454. try {
  455. const response = await fetchPluginInfoFromMarketPlace({ org, name })
  456. return response.data.plugin.category === PluginTypeEnum.model ? response.data.plugin : null
  457. }
  458. catch (error) {
  459. return null
  460. }
  461. },
  462. enabled: !!providerName,
  463. })
  464. }