| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 | import { useCallback } from 'react'import type {  ModelProvider,} from '@/app/components/header/account-setting/model-provider-page/declarations'import { fetchModelProviderModelList } from '@/service/common'import { fetchPluginInfoFromMarketPlace } from '@/service/plugins'import type {  DebugInfo as DebugInfoTypes,  Dependency,  GitHubItemAndMarketPlaceDependency,  InstallPackageResponse,  InstalledPluginListResponse,  PackageDependency,  Permissions,  Plugin,  PluginDetail,  PluginInfoFromMarketPlace,  PluginTask,  PluginType,  PluginsFromMarketplaceByInfoResponse,  PluginsFromMarketplaceResponse,  VersionInfo,  VersionListResponse,  uploadGitHubResponse,} from '@/app/components/plugins/types'import { TaskStatus } from '@/app/components/plugins/types'import { PluginType as PluginTypeEnum } from '@/app/components/plugins/types'import type {  PluginsSearchParams,} from '@/app/components/plugins/marketplace/types'import { get, getMarketplace, post, postMarketplace } from './base'import type { MutateOptions, QueryOptions } from '@tanstack/react-query'import {  useMutation,  useQuery,  useQueryClient,} from '@tanstack/react-query'import { useInvalidateAllBuiltInTools } from './use-tools'import usePermission from '@/app/components/plugins/plugin-page/use-permission'import { uninstallPlugin } from '@/service/plugins'import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'const NAME_SPACE = 'plugins'const useInstalledPluginListKey = [NAME_SPACE, 'installedPluginList']export const useCheckInstalled = ({  pluginIds,  enabled,}: {  pluginIds: string[],  enabled: boolean}) => {  return useQuery<{ plugins: PluginDetail[] }>({    queryKey: [NAME_SPACE, 'checkInstalled', pluginIds],    queryFn: () => post<{ plugins: PluginDetail[] }>('/workspaces/current/plugin/list/installations/ids', {      body: {        plugin_ids: pluginIds,      },    }),    enabled,    staleTime: 0, // always fresh  })}export const useInstalledPluginList = (disable?: boolean) => {  return useQuery<InstalledPluginListResponse>({    queryKey: useInstalledPluginListKey,    queryFn: () => get<InstalledPluginListResponse>('/workspaces/current/plugin/list'),    enabled: !disable,    initialData: !disable ? undefined : { plugins: [] },  })}export const useInvalidateInstalledPluginList = () => {  const queryClient = useQueryClient()  const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools()  return () => {    queryClient.invalidateQueries(      {        queryKey: useInstalledPluginListKey,      })    invalidateAllBuiltInTools()  }}export const useInstallPackageFromMarketPlace = (options?: MutateOptions<InstallPackageResponse, Error, string>) => {  return useMutation({    ...options,    mutationFn: (uniqueIdentifier: string) => {      return post<InstallPackageResponse>('/workspaces/current/plugin/install/marketplace', { body: { plugin_unique_identifiers: [uniqueIdentifier] } })    },  })}export const useUpdatePackageFromMarketPlace = (options?: MutateOptions<InstallPackageResponse, Error, object>) => {  return useMutation({    ...options,    mutationFn: (body: object) => {      return post<InstallPackageResponse>('/workspaces/current/plugin/upgrade/marketplace', {        body,      })    },  })}export const useVersionListOfPlugin = (pluginID: string) => {  return useQuery<{ data: VersionListResponse }>({    enabled: !!pluginID,    queryKey: [NAME_SPACE, 'versions', pluginID],    queryFn: () => getMarketplace<{ data: VersionListResponse }>(`/plugins/${pluginID}/versions`, { params: { page: 1, page_size: 100 } }),  })}export const useInvalidateVersionListOfPlugin = () => {  const queryClient = useQueryClient()  return (pluginID: string) => {    queryClient.invalidateQueries({ queryKey: [NAME_SPACE, 'versions', pluginID] })  }}export const useInstallPackageFromLocal = () => {  return useMutation({    mutationFn: (uniqueIdentifier: string) => {      return post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {        body: { plugin_unique_identifiers: [uniqueIdentifier] },      })    },  })}export const useInstallPackageFromGitHub = () => {  return useMutation({    mutationFn: ({ repoUrl, selectedVersion, selectedPackage, uniqueIdentifier }: {      repoUrl: string      selectedVersion: string      selectedPackage: string      uniqueIdentifier: string    }) => {      return post<InstallPackageResponse>('/workspaces/current/plugin/install/github', {        body: {          repo: repoUrl,          version: selectedVersion,          package: selectedPackage,          plugin_unique_identifier: uniqueIdentifier,        },      })    },  })}export const useUploadGitHub = (payload: {  repo: string  version: string  package: string}) => {  return useQuery({    queryKey: [NAME_SPACE, 'uploadGitHub', payload],    queryFn: () => post<uploadGitHubResponse>('/workspaces/current/plugin/upload/github', {      body: payload,    }),    retry: 0,  })}export const useInstallOrUpdate = ({  onSuccess,}: {  onSuccess?: (res: { success: boolean }[]) => void}) => {  const { mutateAsync: updatePackageFromMarketPlace } = useUpdatePackageFromMarketPlace()  return useMutation({    mutationFn: (data: {      payload: Dependency[],      plugin: Plugin[],      installedInfo: Record<string, VersionInfo>    }) => {      const { payload, plugin, installedInfo } = data      return Promise.all(payload.map(async (item, i) => {        try {          const orgAndName = `${plugin[i]?.org || plugin[i]?.author}/${plugin[i]?.name}`          const installedPayload = installedInfo[orgAndName]          const isInstalled = !!installedPayload          let uniqueIdentifier = ''          if (item.type === 'github') {            const data = item as GitHubItemAndMarketPlaceDependency            // From local bundle don't have data.value.github_plugin_unique_identifier            if (!data.value.github_plugin_unique_identifier) {              const { unique_identifier } = await post<uploadGitHubResponse>('/workspaces/current/plugin/upload/github', {                body: {                  repo: data.value.repo!,                  version: data.value.release! || data.value.version!,                  package: data.value.packages! || data.value.package!,                },              })              uniqueIdentifier = data.value.github_plugin_unique_identifier! || unique_identifier              // has the same version, but not installed              if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {                return {                  success: true,                }              }            }            if (!isInstalled) {              await post<InstallPackageResponse>('/workspaces/current/plugin/install/github', {                body: {                  repo: data.value.repo!,                  version: data.value.release! || data.value.version!,                  package: data.value.packages! || data.value.package!,                  plugin_unique_identifier: uniqueIdentifier,                },              })            }          }          if (item.type === 'marketplace') {            const data = item as GitHubItemAndMarketPlaceDependency            uniqueIdentifier = data.value.plugin_unique_identifier! || plugin[i]?.plugin_id            if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {              return {                success: true,              }            }            if (!isInstalled) {              await post<InstallPackageResponse>('/workspaces/current/plugin/install/marketplace', {                body: {                  plugin_unique_identifiers: [uniqueIdentifier],                },              })            }          }          if (item.type === 'package') {            const data = item as PackageDependency            uniqueIdentifier = data.value.unique_identifier            if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {              return {                success: true,              }            }            if (!isInstalled) {              await post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {                body: {                  plugin_unique_identifiers: [uniqueIdentifier],                },              })            }          }          if (isInstalled) {            if (item.type === 'package') {              await uninstallPlugin(installedPayload.installedId)              await post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {                body: {                  plugin_unique_identifiers: [uniqueIdentifier],                },              })            }            else {              await updatePackageFromMarketPlace({                original_plugin_unique_identifier: installedPayload?.uniqueIdentifier,                new_plugin_unique_identifier: uniqueIdentifier,              })            }          }          return ({ success: true })        }        // eslint-disable-next-line unused-imports/no-unused-vars        catch (e) {          return Promise.resolve({ success: false })        }      }))    },    onSuccess,  })}export const useDebugKey = () => {  return useQuery({    queryKey: [NAME_SPACE, 'debugKey'],    queryFn: () => get<DebugInfoTypes>('/workspaces/current/plugin/debugging-key'),  })}const usePermissionsKey = [NAME_SPACE, 'permissions']export const usePermissions = () => {  return useQuery({    queryKey: usePermissionsKey,    queryFn: () => get<Permissions>('/workspaces/current/plugin/permission/fetch'),  })}export const useInvalidatePermissions = () => {  const queryClient = useQueryClient()  return () => {    queryClient.invalidateQueries(      {        queryKey: usePermissionsKey,      })  }}export const useMutationPermissions = ({  onSuccess,}: {  onSuccess?: () => void}) => {  return useMutation({    mutationFn: (payload: Permissions) => {      return post('/workspaces/current/plugin/permission/change', { body: payload })    },    onSuccess,  })}export const useMutationPluginsFromMarketplace = () => {  return useMutation({    mutationFn: (pluginsSearchParams: PluginsSearchParams) => {      const {        query,        sortBy,        sortOrder,        category,        tags,        exclude,        type,        page = 1,        pageSize = 40,      } = pluginsSearchParams      const pluginOrBundle = type === 'bundle' ? 'bundles' : 'plugins'      return postMarketplace<{ data: PluginsFromMarketplaceResponse }>(`/${pluginOrBundle}/search/basic`, {        body: {          page,          page_size: pageSize,          query,          sort_by: sortBy,          sort_order: sortOrder,          category: category !== 'all' ? category : '',          tags,          exclude,          type,        },      })    },  })}export const useFetchPluginsInMarketPlaceByIds = (unique_identifiers: string[], options?: QueryOptions<{ data: PluginsFromMarketplaceResponse }>) => {  return useQuery({    ...options,    queryKey: [NAME_SPACE, 'fetchPluginsInMarketPlaceByIds', unique_identifiers],    queryFn: () => postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/identifier/batch', {      body: {        unique_identifiers,      },    }),    enabled: unique_identifiers?.filter(i => !!i).length > 0,    retry: 0,  })}export const useFetchPluginsInMarketPlaceByInfo = (infos: Record<string, any>[]) => {  return useQuery({    queryKey: [NAME_SPACE, 'fetchPluginsInMarketPlaceByInfo', infos],    queryFn: () => postMarketplace<{ data: PluginsFromMarketplaceByInfoResponse }>('/plugins/versions/batch', {      body: {        plugin_tuples: infos.map(info => ({          org: info.organization,          name: info.plugin,          version: info.version,        })),      },    }),    enabled: infos?.filter(i => !!i).length > 0,    retry: 0,  })}const usePluginTaskListKey = [NAME_SPACE, 'pluginTaskList']export const usePluginTaskList = (category?: PluginType) => {  const {    canManagement,  } = usePermission()  const { refreshPluginList } = useRefreshPluginList()  const {    data,    isFetched,    refetch,    ...rest  } = useQuery({    enabled: canManagement,    queryKey: usePluginTaskListKey,    queryFn: () => get<{ tasks: PluginTask[] }>('/workspaces/current/plugin/tasks?page=1&page_size=100'),    refetchInterval: (lastQuery) => {      const lastData = lastQuery.state.data      const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed)      const taskAllFailed = lastData?.tasks.every(task => task.status === TaskStatus.failed)      if (taskDone) {        if (lastData?.tasks.length && !taskAllFailed)          refreshPluginList(category ? { category } as any : undefined, !category)        return false      }      return 5000    },  })  const handleRefetch = useCallback(() => {    refetch()  }, [refetch])  return {    data,    pluginTasks: data?.tasks || [],    isFetched,    handleRefetch,    ...rest,  }}export const useMutationClearTaskPlugin = () => {  return useMutation({    mutationFn: ({ taskId, pluginId }: { taskId: string; pluginId: string }) => {      return post<{ success: boolean }>(`/workspaces/current/plugin/tasks/${taskId}/delete/${pluginId}`)    },  })}export const useMutationClearAllTaskPlugin = () => {  return useMutation({    mutationFn: () => {      return post<{ success: boolean }>('/workspaces/current/plugin/tasks/delete_all')    },  })}export const usePluginManifestInfo = (pluginUID: string) => {  return useQuery({    enabled: !!pluginUID,    queryKey: [[NAME_SPACE, 'manifest', pluginUID]],    queryFn: () => getMarketplace<{ data: { plugin: PluginInfoFromMarketPlace, version: { version: string } } }>(`/plugins/${pluginUID}`),    retry: 0,  })}export const useDownloadPlugin = (info: { organization: string; pluginName: string; version: string }, needDownload: boolean) => {  return useQuery({    queryKey: [NAME_SPACE, 'downloadPlugin', info],    queryFn: () => getMarketplace<Blob>(`/plugins/${info.organization}/${info.pluginName}/${info.version}/download`),    enabled: needDownload,    retry: 0,  })}export const useMutationCheckDependencies = () => {  return useMutation({    mutationFn: (appId: string) => {      return get<{ leaked_dependencies: Dependency[] }>(`/apps/imports/${appId}/check-dependencies`)    },  })}export const useModelInList = (currentProvider?: ModelProvider, modelId?: string) => {  return useQuery({    queryKey: ['modelInList', currentProvider?.provider, modelId],    queryFn: async () => {      if (!modelId || !currentProvider) return false      try {        const modelsData = await fetchModelProviderModelList(`/workspaces/current/model-providers/${currentProvider?.provider}/models`)        return !!modelId && !!modelsData.data.find(item => item.model === modelId)      }      catch (error) {        return false      }    },    enabled: !!modelId && !!currentProvider,  })}export const usePluginInfo = (providerName?: string) => {  return useQuery({    queryKey: ['pluginInfo', providerName],    queryFn: async () => {      if (!providerName) return null      const parts = providerName.split('/')      const org = parts[0]      const name = parts[1]      try {        const response = await fetchPluginInfoFromMarketPlace({ org, name })        return response.data.plugin.category === PluginTypeEnum.model ? response.data.plugin : null      }      catch (error) {        return null      }    },    enabled: !!providerName,  })}
 |