mirror of
https://github.com/langgenius/dify.git
synced 2026-04-23 04:06:13 +08:00
Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins
This commit is contained in:
@ -8,7 +8,7 @@ import Button from '@/app/components/base/button'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import Badge, { BadgeState } from '@/app/components/base/badge/index'
|
||||
import { installPackageFromLocal } from '@/service/plugins'
|
||||
import { useInstallPackageFromLocal } from '@/service/use-plugins'
|
||||
import checkTaskStatus from '../../base/check-task-status'
|
||||
import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store'
|
||||
|
||||
@ -33,6 +33,8 @@ const Installed: FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isInstalling, setIsInstalling] = React.useState(false)
|
||||
const { mutateAsync: installPackageFromLocal } = useInstallPackageFromLocal()
|
||||
|
||||
const {
|
||||
check,
|
||||
stop,
|
||||
|
||||
@ -9,7 +9,7 @@ import Button from '@/app/components/base/button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import Badge, { BadgeState } from '@/app/components/base/badge/index'
|
||||
import { installPackageFromMarketPlace } from '@/service/plugins'
|
||||
import { useInstallPackageFromMarketPlace } from '@/service/use-plugins'
|
||||
import checkTaskStatus from '../../base/check-task-status'
|
||||
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
@ -32,6 +32,7 @@ const Installed: FC<Props> = ({
|
||||
onFailed,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { mutateAsync: installPackageFromMarketPlace } = useInstallPackageFromMarketPlace()
|
||||
const [isInstalling, setIsInstalling] = React.useState(false)
|
||||
const {
|
||||
check,
|
||||
|
||||
@ -34,7 +34,7 @@ export type MarketplaceContextValue = {
|
||||
activePluginType: string
|
||||
handleActivePluginTypeChange: (type: string) => void
|
||||
plugins?: Plugin[]
|
||||
setPlugins: (plugins: Plugin[]) => void
|
||||
resetPlugins: () => void
|
||||
sort: PluginsSort
|
||||
handleSortChange: (sort: PluginsSort) => void
|
||||
marketplaceCollectionsFromClient?: MarketplaceCollection[]
|
||||
@ -53,7 +53,7 @@ export const MarketplaceContext = createContext<MarketplaceContextValue>({
|
||||
activePluginType: PLUGIN_TYPE_SEARCH_MAP.all,
|
||||
handleActivePluginTypeChange: () => {},
|
||||
plugins: undefined,
|
||||
setPlugins: () => {},
|
||||
resetPlugins: () => {},
|
||||
sort: DEFAULT_SORT,
|
||||
handleSortChange: () => {},
|
||||
marketplaceCollectionsFromClient: [],
|
||||
@ -91,7 +91,7 @@ export const MarketplaceContextProvider = ({
|
||||
} = useMarketplaceCollectionsAndPlugins()
|
||||
const {
|
||||
plugins,
|
||||
setPlugins,
|
||||
resetPlugins,
|
||||
queryPlugins,
|
||||
queryPluginsWithDebounced,
|
||||
} = useMarketplacePlugins()
|
||||
@ -104,7 +104,7 @@ export const MarketplaceContextProvider = ({
|
||||
queryMarketplaceCollectionsAndPlugins({
|
||||
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
|
||||
})
|
||||
setPlugins(undefined)
|
||||
resetPlugins()
|
||||
|
||||
return
|
||||
}
|
||||
@ -116,7 +116,7 @@ export const MarketplaceContextProvider = ({
|
||||
sortBy: sortRef.current.sortBy,
|
||||
sortOrder: sortRef.current.sortOrder,
|
||||
})
|
||||
}, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, setPlugins])
|
||||
}, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, resetPlugins])
|
||||
|
||||
const handleFilterPluginTagsChange = useCallback((tags: string[]) => {
|
||||
setFilterPluginTags(tags)
|
||||
@ -126,7 +126,7 @@ export const MarketplaceContextProvider = ({
|
||||
queryMarketplaceCollectionsAndPlugins({
|
||||
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
|
||||
})
|
||||
setPlugins(undefined)
|
||||
resetPlugins()
|
||||
|
||||
return
|
||||
}
|
||||
@ -138,7 +138,7 @@ export const MarketplaceContextProvider = ({
|
||||
sortBy: sortRef.current.sortBy,
|
||||
sortOrder: sortRef.current.sortOrder,
|
||||
})
|
||||
}, [queryPlugins, setPlugins, queryMarketplaceCollectionsAndPlugins])
|
||||
}, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins])
|
||||
|
||||
const handleActivePluginTypeChange = useCallback((type: string) => {
|
||||
setActivePluginType(type)
|
||||
@ -148,7 +148,7 @@ export const MarketplaceContextProvider = ({
|
||||
queryMarketplaceCollectionsAndPlugins({
|
||||
category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type,
|
||||
})
|
||||
setPlugins(undefined)
|
||||
resetPlugins()
|
||||
|
||||
return
|
||||
}
|
||||
@ -160,7 +160,7 @@ export const MarketplaceContextProvider = ({
|
||||
sortBy: sortRef.current.sortBy,
|
||||
sortOrder: sortRef.current.sortOrder,
|
||||
})
|
||||
}, [queryPlugins, setPlugins, queryMarketplaceCollectionsAndPlugins])
|
||||
}, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins])
|
||||
|
||||
const handleSortChange = useCallback((sort: PluginsSort) => {
|
||||
setSort(sort)
|
||||
@ -187,7 +187,7 @@ export const MarketplaceContextProvider = ({
|
||||
activePluginType,
|
||||
handleActivePluginTypeChange,
|
||||
plugins,
|
||||
setPlugins,
|
||||
resetPlugins,
|
||||
sort,
|
||||
handleSortChange,
|
||||
marketplaceCollectionsFromClient,
|
||||
|
||||
@ -15,10 +15,10 @@ const Description = async ({
|
||||
return (
|
||||
<>
|
||||
<h1 className='shrink-0 mb-2 text-center title-4xl-semi-bold text-text-primary'>
|
||||
Empower your AI development
|
||||
{t('marketplace.empower')}
|
||||
</h1>
|
||||
<h2 className='shrink-0 flex justify-center items-center text-center body-md-regular text-text-tertiary'>
|
||||
Discover
|
||||
{t('marketplace.discover')}
|
||||
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
{t('category.models')}
|
||||
</span>
|
||||
@ -30,11 +30,11 @@ const Description = async ({
|
||||
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
{t('category.extensions')}
|
||||
</span>
|
||||
and
|
||||
{t('marketplace.and')}
|
||||
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
{t('category.bundles')}
|
||||
</span>
|
||||
in Dify Marketplace
|
||||
{t('marketplace.inDifyMarketplace')}
|
||||
</h2>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -4,7 +4,9 @@ import {
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import type { Plugin } from '../types'
|
||||
import type {
|
||||
Plugin,
|
||||
} from '../types'
|
||||
import type {
|
||||
CollectionsAndPluginsSearchParams,
|
||||
MarketplaceCollection,
|
||||
@ -12,9 +14,9 @@ import type {
|
||||
} from './types'
|
||||
import {
|
||||
getMarketplaceCollectionsAndPlugins,
|
||||
getMarketplacePlugins,
|
||||
} from './utils'
|
||||
import i18n from '@/i18n/i18next-config'
|
||||
import { useMutationPluginsFromMarketplace } from '@/service/use-plugins'
|
||||
|
||||
export const useMarketplaceCollectionsAndPlugins = () => {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
@ -41,28 +43,29 @@ export const useMarketplaceCollectionsAndPlugins = () => {
|
||||
}
|
||||
|
||||
export const useMarketplacePlugins = () => {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [plugins, setPlugins] = useState<Plugin[]>()
|
||||
const {
|
||||
data,
|
||||
mutate,
|
||||
reset,
|
||||
isPending,
|
||||
} = useMutationPluginsFromMarketplace()
|
||||
|
||||
const queryPlugins = useCallback(async (query: PluginsSearchParams) => {
|
||||
setIsLoading(true)
|
||||
const { marketplacePlugins } = await getMarketplacePlugins(query)
|
||||
setIsLoading(false)
|
||||
const queryPlugins = useCallback((pluginsSearchParams: PluginsSearchParams) => {
|
||||
mutate(pluginsSearchParams)
|
||||
}, [mutate])
|
||||
|
||||
setPlugins(marketplacePlugins)
|
||||
}, [])
|
||||
|
||||
const { run: queryPluginsWithDebounced } = useDebounceFn(queryPlugins, {
|
||||
const { run: queryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams) => {
|
||||
mutate(pluginsSearchParams)
|
||||
}, {
|
||||
wait: 500,
|
||||
})
|
||||
|
||||
return {
|
||||
plugins,
|
||||
setPlugins,
|
||||
plugins: data?.data?.plugins,
|
||||
resetPlugins: reset,
|
||||
queryPlugins,
|
||||
queryPluginsWithDebounced,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
isLoading: isPending,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import SearchBoxWrapper from './search-box/search-box-wrapper'
|
||||
import PluginTypeSwitch from './plugin-type-switch'
|
||||
import ListWrapper from './list/list-wrapper'
|
||||
import { getMarketplaceCollectionsAndPlugins } from './utils'
|
||||
import { TanstackQueryIniter } from '@/context/query-client'
|
||||
|
||||
type MarketplaceProps = {
|
||||
locale?: string
|
||||
@ -17,18 +18,20 @@ const Marketplace = async ({
|
||||
const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins()
|
||||
|
||||
return (
|
||||
<MarketplaceContextProvider>
|
||||
<Description locale={locale} />
|
||||
<IntersectionLine />
|
||||
<SearchBoxWrapper locale={locale} />
|
||||
<PluginTypeSwitch locale={locale} />
|
||||
<ListWrapper
|
||||
locale={locale}
|
||||
marketplaceCollections={marketplaceCollections}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
|
||||
showInstallButton={showInstallButton}
|
||||
/>
|
||||
</MarketplaceContextProvider>
|
||||
<TanstackQueryIniter>
|
||||
<MarketplaceContextProvider>
|
||||
<Description locale={locale} />
|
||||
<IntersectionLine />
|
||||
<SearchBoxWrapper locale={locale} />
|
||||
<PluginTypeSwitch locale={locale} />
|
||||
<ListWrapper
|
||||
locale={locale}
|
||||
marketplaceCollections={marketplaceCollections}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
|
||||
showInstallButton={showInstallButton}
|
||||
/>
|
||||
</MarketplaceContextProvider>
|
||||
</TanstackQueryIniter>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ const PluginTypeSwitch = ({
|
||||
const options = [
|
||||
{
|
||||
value: PLUGIN_TYPE_SEARCH_MAP.all,
|
||||
text: 'All',
|
||||
text: t('plugin.category.all'),
|
||||
icon: null,
|
||||
},
|
||||
{
|
||||
|
||||
@ -34,7 +34,7 @@ const PluginSettingModal: FC<Props> = ({
|
||||
const handleSave = useCallback(async () => {
|
||||
await onSave(tempPrivilege)
|
||||
onHide()
|
||||
}, [tempPrivilege])
|
||||
}, [onHide, onSave, tempPrivilege])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import React, { useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
@ -9,28 +8,39 @@ import Indicator from '@/app/components/header/indicator'
|
||||
import ToolItem from '@/app/components/tools/provider/tool-item'
|
||||
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
|
||||
import {
|
||||
fetchBuiltInToolList,
|
||||
fetchCollectionDetail,
|
||||
removeBuiltInToolCredential,
|
||||
updateBuiltInToolCredential,
|
||||
} from '@/service/tools'
|
||||
useBuiltinProviderInfo,
|
||||
useBuiltinTools,
|
||||
useInvalidateBuiltinProviderInfo,
|
||||
useRemoveProviderCredentials,
|
||||
useUpdateProviderCredentials,
|
||||
} from '@/service/use-tools'
|
||||
|
||||
const ActionList = () => {
|
||||
const { t } = useTranslation()
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
||||
const { data: provider } = useSWR(
|
||||
`builtin/${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`,
|
||||
fetchCollectionDetail,
|
||||
)
|
||||
const { data } = useSWR(
|
||||
`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`,
|
||||
fetchBuiltInToolList,
|
||||
)
|
||||
const { data: provider } = useBuiltinProviderInfo(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
|
||||
const invalidateProviderInfo = useInvalidateBuiltinProviderInfo()
|
||||
const { data } = useBuiltinTools(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
|
||||
|
||||
const [showSettingAuth, setShowSettingAuth] = useState(false)
|
||||
|
||||
const handleCredentialSettingUpdate = () => {}
|
||||
const handleCredentialSettingUpdate = () => {
|
||||
invalidateProviderInfo(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
setShowSettingAuth(false)
|
||||
}
|
||||
|
||||
const { mutate: updatePermission } = useUpdateProviderCredentials({
|
||||
onSuccess: handleCredentialSettingUpdate,
|
||||
})
|
||||
|
||||
const { mutate: removePermission } = useRemoveProviderCredentials({
|
||||
onSuccess: handleCredentialSettingUpdate,
|
||||
})
|
||||
|
||||
if (!data || !provider)
|
||||
return null
|
||||
@ -77,24 +87,11 @@ const ActionList = () => {
|
||||
<ConfigCredential
|
||||
collection={provider}
|
||||
onCancel={() => setShowSettingAuth(false)}
|
||||
onSaved={async (value) => {
|
||||
await updateBuiltInToolCredential(provider.name, value)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
handleCredentialSettingUpdate()
|
||||
setShowSettingAuth(false)
|
||||
}}
|
||||
onRemove={async () => {
|
||||
await removeBuiltInToolCredential(provider.name)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
handleCredentialSettingUpdate()
|
||||
setShowSettingAuth(false)
|
||||
}}
|
||||
onSaved={async value => updatePermission({
|
||||
providerName: provider.name,
|
||||
credentials: value,
|
||||
})}
|
||||
onRemove={async () => removePermission(provider.name)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -13,6 +13,8 @@ import Description from '../card/base/description'
|
||||
import Icon from '../card/base/card-icon'
|
||||
import Title from '../card/base/title'
|
||||
import OrgInfo from '../card/base/org-info'
|
||||
import { useGitHubReleases } from '../install-plugin/hooks'
|
||||
import { compareVersion, getLatestVersion } from '@/utils/semver'
|
||||
import OperationDropdown from './operation-dropdown'
|
||||
import PluginInfo from '@/app/components/plugins/plugin-page/plugin-info'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
@ -20,10 +22,13 @@ import Button from '@/app/components/base/button'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { BoxSparkleFill } from '@/app/components/base/icons/src/vender/plugin'
|
||||
import { Github } from '@/app/components/base/icons/src/public/common'
|
||||
import { uninstallPlugin } from '@/service/plugins'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
|
||||
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
@ -32,16 +37,18 @@ const i18nPrefix = 'plugin.action'
|
||||
type Props = {
|
||||
detail: PluginDetail
|
||||
onHide: () => void
|
||||
onDelete: () => void
|
||||
onUpdate: () => void
|
||||
}
|
||||
|
||||
const DetailHeader = ({
|
||||
detail,
|
||||
onHide,
|
||||
onDelete,
|
||||
onUpdate,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const locale = useGetLanguage()
|
||||
const { fetchReleases } = useGitHubReleases()
|
||||
const { setShowUpdatePluginModal } = useModalContext()
|
||||
|
||||
const {
|
||||
installation_id,
|
||||
@ -53,13 +60,51 @@ const DetailHeader = ({
|
||||
} = detail
|
||||
const { author, name, label, description, icon, verified } = detail.declaration
|
||||
const isFromGitHub = source === PluginSource.github
|
||||
// Only plugin installed from GitHub need to check if it's the new version
|
||||
|
||||
const hasNewVersion = useMemo(() => {
|
||||
return source === PluginSource.github && latest_version !== version
|
||||
}, [source, latest_version, version])
|
||||
|
||||
// #plugin TODO# update plugin
|
||||
const handleUpdate = () => { }
|
||||
const handleUpdate = async () => {
|
||||
try {
|
||||
const fetchedReleases = await fetchReleases(author, name)
|
||||
if (fetchedReleases.length === 0)
|
||||
return
|
||||
const versions = fetchedReleases.map(release => release.tag_name)
|
||||
const latestVersion = getLatestVersion(versions)
|
||||
if (compareVersion(latestVersion, version) === 1) {
|
||||
setShowUpdatePluginModal({
|
||||
onSaveCallback: () => {
|
||||
onUpdate()
|
||||
},
|
||||
payload: {
|
||||
type: PluginSource.github,
|
||||
github: {
|
||||
originalPackageInfo: {
|
||||
id: installation_id,
|
||||
repo: meta!.repo,
|
||||
version: meta!.version,
|
||||
package: meta!.package,
|
||||
releases: fetchedReleases,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
else {
|
||||
Toast.notify({
|
||||
type: 'info',
|
||||
message: 'No new version available',
|
||||
})
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: 'Failed to compare versions',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const [isShowPluginInfo, {
|
||||
setTrue: showPluginInfo,
|
||||
@ -82,9 +127,9 @@ const DetailHeader = ({
|
||||
hideDeleting()
|
||||
if (res.success) {
|
||||
hideDeleteConfirm()
|
||||
onDelete()
|
||||
onUpdate()
|
||||
}
|
||||
}, [hideDeleteConfirm, hideDeleting, installation_id, showDeleting, onDelete])
|
||||
}, [hideDeleteConfirm, hideDeleting, installation_id, showDeleting, onUpdate])
|
||||
|
||||
// #plugin TODO# used in apps
|
||||
// const usedInApps = 3
|
||||
@ -141,6 +186,7 @@ const DetailHeader = ({
|
||||
</div>
|
||||
<div className='flex gap-1'>
|
||||
<OperationDropdown
|
||||
source={detail.source}
|
||||
onInfo={showPluginInfo}
|
||||
onCheckVersion={handleUpdate}
|
||||
onRemove={showDeleteConfirm}
|
||||
|
||||
@ -4,62 +4,62 @@ import { useBoolean } from 'ahooks'
|
||||
import { RiDeleteBinLine, RiEditLine, RiLoginCircleLine } from '@remixicon/react'
|
||||
import type { EndpointListItem } from '../types'
|
||||
import EndpointModal from './endpoint-modal'
|
||||
import { NAME_FIELD } from './utils'
|
||||
import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import CopyBtn from '@/app/components/base/copy-btn'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import {
|
||||
deleteEndpoint,
|
||||
disableEndpoint,
|
||||
enableEndpoint,
|
||||
updateEndpoint,
|
||||
} from '@/service/plugins'
|
||||
useDeleteEndpoint,
|
||||
useDisableEndpoint,
|
||||
useEnableEndpoint,
|
||||
useUpdateEndpoint,
|
||||
} from '@/service/use-endpoints'
|
||||
|
||||
type Props = {
|
||||
data: EndpointListItem
|
||||
handleChange: () => void
|
||||
}
|
||||
|
||||
const EndpointCard = ({
|
||||
data,
|
||||
handleChange,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const [active, setActive] = useState(data.enabled)
|
||||
const endpointID = data.id
|
||||
|
||||
// switch
|
||||
const [isShowDisableConfirm, {
|
||||
setTrue: showDisableConfirm,
|
||||
setFalse: hideDisableConfirm,
|
||||
}] = useBoolean(false)
|
||||
const activeEndpoint = async () => {
|
||||
try {
|
||||
await enableEndpoint({
|
||||
url: '/workspaces/current/endpoints/enable',
|
||||
endpointID,
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
const { mutate: enableEndpoint } = useEnableEndpoint({
|
||||
onSuccess: async () => {
|
||||
await handleChange()
|
||||
},
|
||||
onError: () => {
|
||||
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
setActive(false)
|
||||
}
|
||||
}
|
||||
const inactiveEndpoint = async () => {
|
||||
try {
|
||||
await disableEndpoint({
|
||||
url: '/workspaces/current/endpoints/disable',
|
||||
endpointID,
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
setActive(true)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
const { mutate: disableEndpoint } = useDisableEndpoint({
|
||||
onSuccess: async () => {
|
||||
await handleChange()
|
||||
hideDisableConfirm()
|
||||
},
|
||||
onError: () => {
|
||||
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
setActive(false)
|
||||
},
|
||||
})
|
||||
const handleSwitch = (state: boolean) => {
|
||||
if (state) {
|
||||
setActive(true)
|
||||
activeEndpoint()
|
||||
enableEndpoint(endpointID)
|
||||
}
|
||||
else {
|
||||
setActive(false)
|
||||
@ -67,49 +67,49 @@ const EndpointCard = ({
|
||||
}
|
||||
}
|
||||
|
||||
// delete
|
||||
const [isShowDeleteConfirm, {
|
||||
setTrue: showDeleteConfirm,
|
||||
setFalse: hideDeleteConfirm,
|
||||
}] = useBoolean(false)
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await deleteEndpoint({
|
||||
url: '/workspaces/current/endpoints/delete',
|
||||
endpointID,
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
const { mutate: deleteEndpoint } = useDeleteEndpoint({
|
||||
onSuccess: async () => {
|
||||
await handleChange()
|
||||
hideDeleteConfirm()
|
||||
},
|
||||
onError: () => {
|
||||
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
},
|
||||
})
|
||||
|
||||
// update
|
||||
const [isShowEndpointModal, {
|
||||
setTrue: showEndpointModalConfirm,
|
||||
setFalse: hideEndpointModalConfirm,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const formSchemas = useMemo(() => {
|
||||
return toolCredentialToFormSchemas(data.declaration.settings)
|
||||
return toolCredentialToFormSchemas([NAME_FIELD, ...data.declaration.settings])
|
||||
}, [data.declaration.settings])
|
||||
const formValue = useMemo(() => {
|
||||
return addDefaultValue(data.settings, formSchemas)
|
||||
}, [data.settings, formSchemas])
|
||||
|
||||
const handleUpdate = (state: any) => {
|
||||
try {
|
||||
updateEndpoint({
|
||||
url: '/workspaces/current/endpoints',
|
||||
body: {
|
||||
endpoint_id: data.id,
|
||||
settings: state,
|
||||
name: state.name,
|
||||
},
|
||||
})
|
||||
const formValue = {
|
||||
name: data.name,
|
||||
...data.settings,
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
return addDefaultValue(formValue, formSchemas)
|
||||
}, [data.name, data.settings, formSchemas])
|
||||
const { mutate: updateEndpoint } = useUpdateEndpoint({
|
||||
onSuccess: async () => {
|
||||
await handleChange()
|
||||
hideEndpointModalConfirm()
|
||||
},
|
||||
onError: () => {
|
||||
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
},
|
||||
})
|
||||
const handleUpdate = (state: any) => updateEndpoint({
|
||||
endpointID,
|
||||
state,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='p-0.5 bg-background-section-burn rounded-xl'>
|
||||
@ -171,7 +171,7 @@ const EndpointCard = ({
|
||||
hideDisableConfirm()
|
||||
setActive(true)
|
||||
}}
|
||||
onConfirm={inactiveEndpoint}
|
||||
onConfirm={() => disableEndpoint(endpointID)}
|
||||
/>
|
||||
)}
|
||||
{isShowDeleteConfirm && (
|
||||
@ -180,7 +180,7 @@ const EndpointCard = ({
|
||||
title={t('plugin.detailPanel.endpointDeleteTip')}
|
||||
content={<div>{t('plugin.detailPanel.endpointDeleteContent', { name: data.name })}</div>}
|
||||
onCancel={hideDeleteConfirm}
|
||||
onConfirm={handleDelete}
|
||||
onConfirm={() => deleteEndpoint(endpointID)}
|
||||
/>
|
||||
)}
|
||||
{isShowEndpointModal && (
|
||||
|
||||
@ -1,65 +1,62 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSWR from 'swr'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import EndpointModal from './endpoint-modal'
|
||||
import EndpointCard from './endpoint-card'
|
||||
import { NAME_FIELD } from './utils'
|
||||
import { toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
|
||||
import {
|
||||
createEndpoint,
|
||||
fetchEndpointList,
|
||||
} from '@/service/plugins'
|
||||
useCreateEndpoint,
|
||||
useEndpointList,
|
||||
useInvalidateEndpointList,
|
||||
} from '@/service/use-endpoints'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const EndpointList = () => {
|
||||
type Props = {
|
||||
showTopBorder?: boolean
|
||||
}
|
||||
const EndpointList = ({ showTopBorder }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const pluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
||||
const pluginUniqueID = pluginDetail.plugin_unique_identifier
|
||||
const declaration = pluginDetail.declaration.endpoint
|
||||
const { data } = useSWR(
|
||||
{
|
||||
url: '/workspaces/current/endpoints/list/plugin',
|
||||
params: {
|
||||
plugin_id: pluginDetail.plugin_id,
|
||||
page: 1,
|
||||
page_size: 100,
|
||||
},
|
||||
},
|
||||
fetchEndpointList,
|
||||
)
|
||||
const { data } = useEndpointList(pluginDetail.plugin_id)
|
||||
const invalidateEndpointList = useInvalidateEndpointList()
|
||||
|
||||
const [isShowEndpointModal, {
|
||||
setTrue: showEndpointModal,
|
||||
setFalse: hideEndpointModal,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const formSchemas = useMemo(() => {
|
||||
return toolCredentialToFormSchemas(declaration.settings)
|
||||
return toolCredentialToFormSchemas([NAME_FIELD, ...declaration.settings])
|
||||
}, [declaration.settings])
|
||||
|
||||
const handleCreate = (state: any) => {
|
||||
try {
|
||||
createEndpoint({
|
||||
url: '/workspaces/current/endpoints',
|
||||
body: {
|
||||
plugin_unique_identifier: pluginUniqueID,
|
||||
settings: state,
|
||||
name: state.name,
|
||||
},
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
const { mutate: createEndpoint } = useCreateEndpoint({
|
||||
onSuccess: async () => {
|
||||
await invalidateEndpointList(pluginDetail.plugin_id)
|
||||
hideEndpointModal()
|
||||
},
|
||||
onError: () => {
|
||||
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
},
|
||||
})
|
||||
|
||||
const handleCreate = (state: any) => createEndpoint({
|
||||
pluginUniqueID,
|
||||
state,
|
||||
})
|
||||
|
||||
if (!data)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='px-4 py-2 border-t border-divider-subtle'>
|
||||
<div className={cn('px-4 py-2 border-divider-subtle', showTopBorder && 'border-t')}>
|
||||
<div className='mb-1 h-6 flex items-center justify-between text-text-secondary system-sm-semibold-uppercase'>
|
||||
<div className='flex items-center gap-0.5'>
|
||||
{t('plugin.detailPanel.endpoints')}
|
||||
@ -81,6 +78,7 @@ const EndpointList = () => {
|
||||
<EndpointCard
|
||||
key={index}
|
||||
data={item}
|
||||
handleChange={() => invalidateEndpointList(pluginDetail.plugin_id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -10,11 +10,11 @@ import { usePluginPageContext } from '@/app/components/plugins/plugin-page/conte
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
onDelete: () => void
|
||||
onUpdate: () => void
|
||||
}
|
||||
|
||||
const PluginDetailPanel: FC<Props> = ({
|
||||
onDelete,
|
||||
onUpdate,
|
||||
}) => {
|
||||
const pluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
||||
const setCurrentPluginDetail = usePluginPageContext(v => v.setCurrentPluginDetail)
|
||||
@ -39,11 +39,11 @@ const PluginDetailPanel: FC<Props> = ({
|
||||
<DetailHeader
|
||||
detail={pluginDetail}
|
||||
onHide={handleHide}
|
||||
onDelete={onDelete}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
<div className='grow overflow-y-auto'>
|
||||
{!!pluginDetail.declaration.endpoint && <EndpointList />}
|
||||
{!!pluginDetail.declaration.tool && <ActionList />}
|
||||
{!!pluginDetail.declaration.endpoint && <EndpointList showTopBorder={!!pluginDetail.declaration.tool} />}
|
||||
{!!pluginDetail.declaration.model && <ModelList />}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -1,105 +0,0 @@
|
||||
import { PluginSource, PluginType } from '../types'
|
||||
|
||||
export const toolNotion = {
|
||||
id: 'dlfajkgjdga-dfjalksjfglkds-dfjakld',
|
||||
created_at: '2024-10-16 16:05:33',
|
||||
updated_at: '2024-10-16 16:05:33',
|
||||
name: 'notion page search',
|
||||
plugin_id: 'Notion/notion-page-search',
|
||||
plugin_unique_identifier: 'Notion/notion-page-search:1.2.0@fldsjflkdsajfldsakajfkls',
|
||||
declaration: {
|
||||
version: '1.2.0',
|
||||
author: 'Notion',
|
||||
name: 'notion page search',
|
||||
category: PluginType.tool,
|
||||
icon: 'https://via.placeholder.com/150',
|
||||
label: {
|
||||
'en-US': 'Notion Page Search',
|
||||
'zh-Hans': 'Notion 页面搜索',
|
||||
},
|
||||
description: {
|
||||
'en-US': 'Description: Search Notion pages and open visited ones faster. No admin access required.More and more info...More and more info...More and more info...',
|
||||
'zh-Hans': '搜索 Notion 页面并更快地打开已访问的页面。无需管理员访问权限。More and more info...More and more info...More and more info...',
|
||||
},
|
||||
created_at: '2024-10-16 16:05:33',
|
||||
resource: {},
|
||||
plugins: {},
|
||||
endpoint: {
|
||||
settings: [
|
||||
{
|
||||
type: 'secret-input',
|
||||
name: 'api-key',
|
||||
required: true,
|
||||
default: null,
|
||||
options: null,
|
||||
label: {
|
||||
en_US: 'API-key',
|
||||
zh_Hans: 'API-key',
|
||||
},
|
||||
help: null,
|
||||
url: null,
|
||||
placeholder: {
|
||||
en_US: 'Please input your API key',
|
||||
zh_Hans: '请输入你的 API key',
|
||||
},
|
||||
},
|
||||
],
|
||||
endpoints: [
|
||||
{ path: '/duck/<app_id>', method: 'GET' },
|
||||
{ path: '/neko', method: 'GET' },
|
||||
],
|
||||
},
|
||||
tool: null, // TODO
|
||||
verified: true,
|
||||
},
|
||||
installation_id: 'jflkdsjoewingljlsadjgoijg-dkfjldajglkajglask-dlfkajdg',
|
||||
tenant_id: 'jflkdsjoewingljlsadjgoijg',
|
||||
endpoints_setups: 2,
|
||||
endpoints_active: 1,
|
||||
version: '1.2.0',
|
||||
source: PluginSource.marketplace,
|
||||
meta: null,
|
||||
}
|
||||
|
||||
export const toolNotionEndpoints = [
|
||||
{
|
||||
id: 'dlfajkgjdga-dfjalksjfglkds-dfjakld',
|
||||
created_at: '2024-10-16 16:05:33',
|
||||
updated_at: '2024-10-16 16:05:33',
|
||||
settings: {
|
||||
'api-key': '*******',
|
||||
},
|
||||
tenant_id: 'jflkdsjoewingljlsadjgoijg',
|
||||
plugin_id: 'Notion/notion-page-search',
|
||||
expired_at: '2024-10-16 16:05:33',
|
||||
declaration: {
|
||||
settings: [
|
||||
{
|
||||
type: 'secret-input',
|
||||
name: 'api-key',
|
||||
required: true,
|
||||
default: null,
|
||||
options: null,
|
||||
label: {
|
||||
en_US: 'API-key',
|
||||
zh_Hans: 'API-key',
|
||||
},
|
||||
help: null,
|
||||
url: null,
|
||||
placeholder: {
|
||||
en_US: 'Please input your API key',
|
||||
zh_Hans: '请输入你的 API key',
|
||||
},
|
||||
},
|
||||
],
|
||||
endpoints: [
|
||||
{ path: '/duck/<app_id>', method: 'GET' },
|
||||
{ path: '/neko', method: 'GET' },
|
||||
],
|
||||
},
|
||||
name: 'default',
|
||||
enabled: true,
|
||||
url: 'http://localhost:5002/e/45rj9V4TRxAjL0I2wXRZgZdXjdHEKBh8',
|
||||
hook_id: '45rj9V4TRxAjL0I2wXRZgZdXjdHEKBh8',
|
||||
},
|
||||
]
|
||||
@ -1,19 +1,14 @@
|
||||
import React from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
|
||||
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
|
||||
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
|
||||
import { fetchModelProviderModelList } from '@/service/common'
|
||||
import { useModelProviderModelList } from '@/service/use-models'
|
||||
|
||||
const ModelList = () => {
|
||||
const { t } = useTranslation()
|
||||
const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
||||
|
||||
const { data: res } = useSWR(
|
||||
`/workspaces/current/model-providers/${currentPluginDetail.plugin_id}/${currentPluginDetail.name}/models`,
|
||||
fetchModelProviderModelList,
|
||||
)
|
||||
const { data: res } = useModelProviderModelList(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
|
||||
|
||||
if (!res)
|
||||
return null
|
||||
@ -21,7 +16,7 @@ const ModelList = () => {
|
||||
return (
|
||||
<div className='px-4 py-2'>
|
||||
<div className='mb-1 h-6 flex items-center text-text-secondary system-sm-semibold-uppercase'>{t('plugin.detailPanel.modelNum', { num: res.data.length })}</div>
|
||||
<div className='h-8 flex items-center'>
|
||||
<div className='flex flex-col'>
|
||||
{res.data.map(model => (
|
||||
<div key={model.model} className='h-6 py-1 flex items-center'>
|
||||
<ModelIcon
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PluginSource } from '../types'
|
||||
import { RiArrowRightUpLine, RiMoreFill } from '@remixicon/react'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
// import Button from '@/app/components/base/button'
|
||||
@ -13,6 +14,7 @@ import {
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
source: PluginSource
|
||||
onInfo: () => void
|
||||
onCheckVersion: () => void
|
||||
onRemove: () => void
|
||||
@ -20,10 +22,11 @@ type Props = {
|
||||
}
|
||||
|
||||
const OperationDropdown: FC<Props> = ({
|
||||
source,
|
||||
detailUrl,
|
||||
onInfo,
|
||||
onCheckVersion,
|
||||
onRemove,
|
||||
detailUrl,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, doSetOpen] = useState(false)
|
||||
@ -56,25 +59,33 @@ const OperationDropdown: FC<Props> = ({
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-50'>
|
||||
<div className='w-[160px] p-1 bg-components-panel-bg-blur rounded-xl border-[0.5px] border-components-panel-border shadow-lg'>
|
||||
<div
|
||||
onClick={() => {
|
||||
onInfo()
|
||||
handleTrigger()
|
||||
}}
|
||||
className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'
|
||||
>{t('plugin.detailPanel.operation.info')}</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
onCheckVersion()
|
||||
handleTrigger()
|
||||
}}
|
||||
className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'
|
||||
>{t('plugin.detailPanel.operation.checkUpdate')}</div>
|
||||
<a href={detailUrl} target='_blank' className='flex items-center px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>
|
||||
<span className='grow'>{t('plugin.detailPanel.operation.viewDetail')}</span>
|
||||
<RiArrowRightUpLine className='shrink-0 w-3.5 h-3.5 text-text-tertiary' />
|
||||
</a>
|
||||
<div className='my-1 h-px bg-divider-subtle'></div>
|
||||
{source === PluginSource.github && (
|
||||
<div
|
||||
onClick={() => {
|
||||
onInfo()
|
||||
handleTrigger()
|
||||
}}
|
||||
className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'
|
||||
>{t('plugin.detailPanel.operation.info')}</div>
|
||||
)}
|
||||
{source === PluginSource.github && (
|
||||
<div
|
||||
onClick={() => {
|
||||
onCheckVersion()
|
||||
handleTrigger()
|
||||
}}
|
||||
className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'
|
||||
>{t('plugin.detailPanel.operation.checkUpdate')}</div>
|
||||
)}
|
||||
{source === PluginSource.marketplace && (
|
||||
<a href={detailUrl} target='_blank' className='flex items-center px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>
|
||||
<span className='grow'>{t('plugin.detailPanel.operation.viewDetail')}</span>
|
||||
<RiArrowRightUpLine className='shrink-0 w-3.5 h-3.5 text-text-tertiary' />
|
||||
</a>
|
||||
)}
|
||||
{(source === PluginSource.marketplace || source === PluginSource.github) && (
|
||||
<div className='my-1 h-px bg-divider-subtle'></div>
|
||||
)}
|
||||
<div
|
||||
onClick={() => {
|
||||
onRemove()
|
||||
|
||||
21
web/app/components/plugins/plugin-detail-panel/utils.ts
Normal file
21
web/app/components/plugins/plugin-detail-panel/utils.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
export const NAME_FIELD = {
|
||||
type: FormTypeEnum.textInput,
|
||||
name: 'name',
|
||||
label: {
|
||||
en_US: 'Endpoint Name',
|
||||
zh_Hans: '端点名称',
|
||||
ja_JP: 'エンドポイント名',
|
||||
pt_BR: 'Nome do ponto final',
|
||||
},
|
||||
placeholder: {
|
||||
en_US: 'Endpoint Name',
|
||||
zh_Hans: '端点名称',
|
||||
ja_JP: 'エンドポイント名',
|
||||
pt_BR: 'Nome do ponto final',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
help: null,
|
||||
}
|
||||
@ -11,16 +11,13 @@ import {
|
||||
useContextSelector,
|
||||
} from 'use-context-selector'
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import type { Permissions, PluginDetail } from '../types'
|
||||
import type { PluginDetail } from '../types'
|
||||
import type { FilterState } from './filter-management'
|
||||
import { PermissionType } from '../types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
||||
|
||||
export type PluginPageContextValue = {
|
||||
containerRef: React.RefObject<HTMLDivElement>
|
||||
permissions: Permissions
|
||||
setPermissions: (permissions: PluginPageContextValue['permissions']) => void
|
||||
currentPluginDetail: PluginDetail | undefined
|
||||
setCurrentPluginDetail: (plugin: PluginDetail) => void
|
||||
filters: FilterState
|
||||
@ -32,21 +29,16 @@ export type PluginPageContextValue = {
|
||||
|
||||
export const PluginPageContext = createContext<PluginPageContextValue>({
|
||||
containerRef: { current: null },
|
||||
permissions: {
|
||||
install_permission: PermissionType.noOne,
|
||||
debug_permission: PermissionType.noOne,
|
||||
},
|
||||
setPermissions: () => {},
|
||||
currentPluginDetail: undefined,
|
||||
setCurrentPluginDetail: () => {},
|
||||
setCurrentPluginDetail: () => { },
|
||||
filters: {
|
||||
categories: [],
|
||||
tags: [],
|
||||
searchQuery: '',
|
||||
},
|
||||
setFilters: () => {},
|
||||
setFilters: () => { },
|
||||
activeTab: '',
|
||||
setActiveTab: () => {},
|
||||
setActiveTab: () => { },
|
||||
options: [],
|
||||
})
|
||||
|
||||
@ -63,10 +55,6 @@ export const PluginPageContextProvider = ({
|
||||
}: PluginPageContextProviderProps) => {
|
||||
const { t } = useTranslation()
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [permissions, setPermissions] = useState<PluginPageContextValue['permissions']>({
|
||||
install_permission: PermissionType.noOne,
|
||||
debug_permission: PermissionType.noOne,
|
||||
})
|
||||
const [filters, setFilters] = useState<FilterState>({
|
||||
categories: [],
|
||||
tags: [],
|
||||
@ -93,8 +81,6 @@ export const PluginPageContextProvider = ({
|
||||
<PluginPageContext.Provider
|
||||
value={{
|
||||
containerRef,
|
||||
permissions,
|
||||
setPermissions,
|
||||
currentPluginDetail,
|
||||
setCurrentPluginDetail,
|
||||
filters,
|
||||
|
||||
@ -206,7 +206,7 @@ const PluginPage = ({
|
||||
|
||||
{showPluginSettingModal && (
|
||||
<PermissionSetModal
|
||||
payload={permissions}
|
||||
payload={permissions!}
|
||||
onHide={setHidePluginSettingModal}
|
||||
onSave={setPermissions}
|
||||
/>
|
||||
|
||||
@ -48,7 +48,7 @@ const PluginsPanel = () => {
|
||||
) : (
|
||||
<Empty />
|
||||
)}
|
||||
<PluginDetailPanel onDelete={() => invalidateInstalledPluginList()}/>
|
||||
<PluginDetailPanel onUpdate={() => invalidateInstalledPluginList()}/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import { useEffect } from 'react'
|
||||
import type { Permissions } from '../types'
|
||||
import { PermissionType } from '../types'
|
||||
import {
|
||||
usePluginPageContext,
|
||||
} from './context'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { updatePermission as doUpdatePermission, fetchPermission } from '@/service/plugins'
|
||||
import Toast from '../../base/toast'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useInvalidatePermissions, useMutationPermissions, usePermissions } from '@/service/use-plugins'
|
||||
|
||||
const hasPermission = (permission: PermissionType, isAdmin: boolean) => {
|
||||
const hasPermission = (permission: PermissionType | undefined, isAdmin: boolean) => {
|
||||
if (!permission)
|
||||
return false
|
||||
if (permission === PermissionType.noOne)
|
||||
return false
|
||||
|
||||
@ -22,29 +19,26 @@ const hasPermission = (permission: PermissionType, isAdmin: boolean) => {
|
||||
const usePermission = () => {
|
||||
const { t } = useTranslation()
|
||||
const { isCurrentWorkspaceManager, isCurrentWorkspaceOwner } = useAppContext()
|
||||
const [permissions, setPermissions] = usePluginPageContext(v => [v.permissions, v.setPermissions])
|
||||
const { data: permissions } = usePermissions()
|
||||
const invalidatePermissions = useInvalidatePermissions()
|
||||
const { mutate: updatePermission, isPending: isUpdatePending } = useMutationPermissions({
|
||||
onSuccess: () => {
|
||||
invalidatePermissions()
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
},
|
||||
})
|
||||
const isAdmin = isCurrentWorkspaceManager || isCurrentWorkspaceOwner
|
||||
|
||||
const updatePermission = async (permission: Permissions) => {
|
||||
await doUpdatePermission(permission)
|
||||
setPermissions(permission)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const permission = await fetchPermission()
|
||||
setPermissions(permission)
|
||||
})()
|
||||
}, [])
|
||||
return {
|
||||
canManagement: hasPermission(permissions.install_permission, isAdmin),
|
||||
canDebugger: hasPermission(permissions.debug_permission, isAdmin),
|
||||
canManagement: hasPermission(permissions?.install_permission, isAdmin),
|
||||
canDebugger: hasPermission(permissions?.debug_permission, isAdmin),
|
||||
canSetPermissions: isAdmin,
|
||||
permissions,
|
||||
setPermissions: updatePermission,
|
||||
isUpdatePending,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,11 +19,13 @@ import { useBoolean } from 'ahooks'
|
||||
type Props = {
|
||||
className?: string
|
||||
payload: Plugin
|
||||
onSuccess: () => void
|
||||
}
|
||||
|
||||
const ProviderCard: FC<Props> = ({
|
||||
className,
|
||||
payload,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isShowInstallFromMarketplace, {
|
||||
@ -84,7 +86,10 @@ const ProviderCard: FC<Props> = ({
|
||||
manifest={payload as any}
|
||||
uniqueIdentifier={payload.latest_package_identifier}
|
||||
onClose={hideInstallFromMarketplace}
|
||||
onSuccess={hideInstallFromMarketplace}
|
||||
onSuccess={() => {
|
||||
onSuccess()
|
||||
hideInstallFromMarketplace()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -194,19 +194,10 @@ export type GitHubUrlInfo = {
|
||||
}
|
||||
|
||||
// endpoint
|
||||
export type CreateEndpointRequest = {
|
||||
plugin_unique_identifier: string
|
||||
settings: Record<string, any>
|
||||
name: string
|
||||
}
|
||||
export type EndpointOperationResponse = {
|
||||
result: 'success' | 'error'
|
||||
}
|
||||
export type EndpointsRequest = {
|
||||
page_size: number
|
||||
page: number
|
||||
plugin_id: string
|
||||
}
|
||||
|
||||
export type EndpointsResponse = {
|
||||
endpoints: EndpointListItem[]
|
||||
has_more: boolean
|
||||
@ -301,3 +292,7 @@ export type InstalledPluginListResponse = {
|
||||
export type UninstallPluginResponse = {
|
||||
success: boolean
|
||||
}
|
||||
|
||||
export type PluginsFromMarketplaceResponse = {
|
||||
plugins: Plugin[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user