mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 11:42:29 +08:00
Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins
This commit is contained in:
commit
dbc10425c8
|
@ -15,20 +15,20 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||
<>
|
||||
<GA gaType={GaType.admin} />
|
||||
<SwrInitor>
|
||||
<AppContextProvider>
|
||||
<EventEmitterContextProvider>
|
||||
<ProviderContextProvider>
|
||||
<ModalContextProvider>
|
||||
<HeaderWrapper>
|
||||
<Header />
|
||||
</HeaderWrapper>
|
||||
<TanstackQueryIniter>
|
||||
<TanstackQueryIniter>
|
||||
<AppContextProvider>
|
||||
<EventEmitterContextProvider>
|
||||
<ProviderContextProvider>
|
||||
<ModalContextProvider>
|
||||
<HeaderWrapper>
|
||||
<Header />
|
||||
</HeaderWrapper>
|
||||
{children}
|
||||
</TanstackQueryIniter>
|
||||
</ModalContextProvider>
|
||||
</ProviderContextProvider>
|
||||
</EventEmitterContextProvider>
|
||||
</AppContextProvider>
|
||||
</ModalContextProvider>
|
||||
</ProviderContextProvider>
|
||||
</EventEmitterContextProvider>
|
||||
</AppContextProvider>
|
||||
</TanstackQueryIniter>
|
||||
</SwrInitor>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -222,7 +222,7 @@ const ModelProviderPage = ({ searchText }: Props) => {
|
|||
{!collapse && !isPluginsLoading && (
|
||||
<div className='grid grid-cols-2 gap-2'>
|
||||
{plugins.map(plugin => (
|
||||
<ProviderCard key={plugin.plugin_id} payload={plugin} />
|
||||
<ProviderCard key={plugin.plugin_id} payload={plugin} onSuccess={updateModelProviders} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -50,15 +50,15 @@ const ModelList: FC<ModelListProps> = ({
|
|||
|
||||
return (
|
||||
<div className='px-2 pb-2 rounded-b-xl'>
|
||||
<div className='py-1 bg-white rounded-lg'>
|
||||
<div className='py-1 bg-components-panel-bg rounded-lg'>
|
||||
<div className='flex items-center pl-1 pr-[3px]'>
|
||||
<span className='group shrink-0 flex items-center mr-2'>
|
||||
<span className='group-hover:hidden inline-flex pl-1 pr-1.5 h-6 leading-6 text-xs font-medium text-gray-500'>
|
||||
<span className='group-hover:hidden inline-flex items-center pl-1 pr-1.5 h-6 system-xs-medium text-text-tertiary'>
|
||||
{t('common.modelProvider.modelsNum', { num: models.length })}
|
||||
<RiArrowRightSLine className='mr-0.5 w-4 h-4 rotate-90' />
|
||||
</span>
|
||||
<span
|
||||
className='hidden group-hover:inline-flex items-center pl-1 pr-1.5 h-6 text-xs font-medium text-gray-500 bg-gray-50 cursor-pointer rounded-lg'
|
||||
className='hidden group-hover:inline-flex items-center pl-1 pr-1.5 h-6 system-xs-medium text-text-tertiary bg-state-base-hover cursor-pointer rounded-lg'
|
||||
onClick={() => onCollapse()}
|
||||
>
|
||||
{t('common.modelProvider.modelsNum', { num: models.length })}
|
||||
|
|
|
@ -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[]
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
|
|||
} = useMarketplaceCollectionsAndPlugins()
|
||||
const {
|
||||
plugins,
|
||||
setPlugins,
|
||||
resetPlugins,
|
||||
queryPlugins,
|
||||
queryPluginsWithDebounced,
|
||||
isLoading: isPluginsLoading,
|
||||
|
@ -37,9 +37,9 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
|
|||
}
|
||||
else {
|
||||
queryMarketplaceCollectionsAndPlugins()
|
||||
setPlugins(undefined)
|
||||
resetPlugins()
|
||||
}
|
||||
}, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, setPlugins])
|
||||
}, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, resetPlugins])
|
||||
|
||||
return {
|
||||
isLoading: isLoading || isPluginsLoading,
|
||||
|
|
|
@ -31,9 +31,11 @@ const Marketplace = ({
|
|||
onClick={() => onMarketplaceScroll()}
|
||||
/>
|
||||
<div className='sticky top-0 pt-5 pb-3 bg-background-default-subtle z-10'>
|
||||
<div className='title-2xl-semi-bold bg-gradient-to-r from-[rgba(11,165,236,0.95)] to-[rgba(21,90,239,0.95)] bg-clip-text text-transparent'>More from Marketplace</div>
|
||||
<div className='title-2xl-semi-bold bg-gradient-to-r from-[rgba(11,165,236,0.95)] to-[rgba(21,90,239,0.95)] bg-clip-text text-transparent'>
|
||||
{t('plugin.marketplace.moreFrom')}
|
||||
</div>
|
||||
<div className='flex items-center text-center body-md-regular text-text-tertiary'>
|
||||
Discover
|
||||
{t('plugin.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('plugin.category.models')}
|
||||
</span>
|
||||
|
@ -45,11 +47,11 @@ const Marketplace = ({
|
|||
<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('plugin.category.extensions')}
|
||||
</span>
|
||||
and
|
||||
{t('plugin.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('plugin.category.bundles')}
|
||||
</span>
|
||||
in Dify Marketplace
|
||||
{t('plugin.marketplace.inDifyMarketplace')}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
|
|
|
@ -80,7 +80,7 @@ export type Tool = {
|
|||
export type ToolCredential = {
|
||||
name: string
|
||||
label: TypeWithI18N
|
||||
help: TypeWithI18N
|
||||
help: TypeWithI18N | null
|
||||
placeholder: TypeWithI18N
|
||||
type: string
|
||||
required: boolean
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
const translation = {
|
||||
plugins: {
|
||||
title: 'Plugins',
|
||||
},
|
||||
discover: {
|
||||
title: 'Explore Marketplace',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
|
@ -1,5 +1,6 @@
|
|||
const translation = {
|
||||
category: {
|
||||
all: 'All',
|
||||
models: 'models',
|
||||
tools: 'tools',
|
||||
extensions: 'extensions',
|
||||
|
@ -116,6 +117,13 @@ const translation = {
|
|||
error: {
|
||||
inValidGitHubUrl: 'Invalid GitHub URL. Please enter a valid URL in the format: https://github.com/owner/repo',
|
||||
},
|
||||
marketplace: {
|
||||
empower: 'Empower your AI development',
|
||||
discover: 'Discover',
|
||||
and: 'and',
|
||||
inDifyMarketplace: 'in Dify Marketplace',
|
||||
moreFrom: 'More from Marketplace',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
const translation = {
|
||||
plugins: {
|
||||
title: '插件',
|
||||
},
|
||||
discover: {
|
||||
title: '探索市场',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
|
@ -1,5 +1,6 @@
|
|||
const translation = {
|
||||
category: {
|
||||
all: '全部',
|
||||
models: '模型',
|
||||
tools: '工具',
|
||||
extensions: '扩展',
|
||||
|
@ -116,6 +117,13 @@ const translation = {
|
|||
error: {
|
||||
inValidGitHubUrl: '无效的 GitHub URL。请输入格式为 https://github.com/owner/repo 的有效 URL',
|
||||
},
|
||||
marketplace: {
|
||||
empower: '助力您的 AI 开发',
|
||||
discover: '探索',
|
||||
and: '和',
|
||||
inDifyMarketplace: '在 Dify 市场中',
|
||||
moreFrom: '更多来自市场',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
"js-cookie": "^3.0.5",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"katex": "^0.16.11",
|
||||
"ky": "^1.7.2",
|
||||
"lamejs": "^1.2.1",
|
||||
"lexical": "^0.18.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
@ -84,9 +85,9 @@
|
|||
"react-hook-form": "^7.53.1",
|
||||
"react-i18next": "^15.1.0",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-multi-email": "^1.0.25",
|
||||
"react-papaparse": "^4.4.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-slider": "^2.0.6",
|
||||
"react-sortablejs": "^6.1.4",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
|
|
|
@ -133,6 +133,9 @@ importers:
|
|||
katex:
|
||||
specifier: ^0.16.11
|
||||
version: 0.16.11
|
||||
ky:
|
||||
specifier: ^1.7.2
|
||||
version: 1.7.2
|
||||
lamejs:
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
|
@ -5612,6 +5615,10 @@ packages:
|
|||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
ky@1.7.2:
|
||||
resolution: {integrity: sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
lamejs@1.2.1:
|
||||
resolution: {integrity: sha512-s7bxvjvYthw6oPLCm5pFxvA84wUROODB8jEO2+CE1adhKgrIvVOlmMgY8zyugxGrvRaDHNJanOiS21/emty6dQ==}
|
||||
|
||||
|
@ -14744,6 +14751,8 @@ snapshots:
|
|||
|
||||
kleur@4.1.5: {}
|
||||
|
||||
ky@1.7.2: {}
|
||||
|
||||
lamejs@1.2.1:
|
||||
dependencies:
|
||||
use-strict: 1.0.1
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { API_PREFIX, IS_CE_EDITION, MARKETPLACE_API_PREFIX, PUBLIC_API_PREFIX } from '@/config'
|
||||
import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config'
|
||||
import { refreshAccessTokenOrRelogin } from './refresh-token'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type'
|
||||
|
@ -17,27 +17,10 @@ import type {
|
|||
WorkflowStartedResponse,
|
||||
} from '@/types/workflow'
|
||||
import { removeAccessToken } from '@/app/components/share/utils'
|
||||
import type { FetchOptionType, ResponseError } from './fetch'
|
||||
import { ContentType, base, baseOptions, getPublicToken } from './fetch'
|
||||
const TIME_OUT = 100000
|
||||
|
||||
const ContentType = {
|
||||
json: 'application/json',
|
||||
stream: 'text/event-stream',
|
||||
audio: 'audio/mpeg',
|
||||
form: 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
download: 'application/octet-stream', // for download
|
||||
upload: 'multipart/form-data', // for upload
|
||||
}
|
||||
|
||||
const baseOptions = {
|
||||
method: 'GET',
|
||||
mode: 'cors',
|
||||
credentials: 'include', // always send cookies、HTTP Basic authentication.
|
||||
headers: new Headers({
|
||||
'Content-Type': ContentType.json,
|
||||
}),
|
||||
redirect: 'follow',
|
||||
}
|
||||
|
||||
export type IOnDataMoreInfo = {
|
||||
conversationId?: string
|
||||
taskId?: string
|
||||
|
@ -100,17 +83,6 @@ export type IOtherOptions = {
|
|||
onTextReplace?: IOnTextReplace
|
||||
}
|
||||
|
||||
type ResponseError = {
|
||||
code: string
|
||||
message: string
|
||||
status: number
|
||||
}
|
||||
|
||||
type FetchOptionType = Omit<RequestInit, 'body'> & {
|
||||
params?: Record<string, any>
|
||||
body?: BodyInit | Record<string, any> | null
|
||||
}
|
||||
|
||||
function unicodeToChar(text: string) {
|
||||
if (!text)
|
||||
return ''
|
||||
|
@ -277,153 +249,13 @@ const handleStream = (
|
|||
read()
|
||||
}
|
||||
|
||||
const baseFetch = <T>(
|
||||
url: string,
|
||||
fetchOptions: FetchOptionType,
|
||||
{
|
||||
isPublicAPI = false,
|
||||
isMarketplaceAPI = false,
|
||||
bodyStringify = true,
|
||||
needAllResponseContent,
|
||||
deleteContentType,
|
||||
getAbortController,
|
||||
silent,
|
||||
}: IOtherOptions,
|
||||
): Promise<T> => {
|
||||
const options: typeof baseOptions & FetchOptionType = Object.assign({}, baseOptions, fetchOptions)
|
||||
if (isMarketplaceAPI)
|
||||
options.credentials = 'omit'
|
||||
|
||||
if (getAbortController) {
|
||||
const abortController = new AbortController()
|
||||
getAbortController(abortController)
|
||||
options.signal = abortController.signal
|
||||
}
|
||||
|
||||
if (isPublicAPI) {
|
||||
const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0]
|
||||
const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' })
|
||||
let accessTokenJson = { [sharedToken]: '' }
|
||||
try {
|
||||
accessTokenJson = JSON.parse(accessToken)
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
}
|
||||
options.headers.set('Authorization', `Bearer ${accessTokenJson[sharedToken]}`)
|
||||
}
|
||||
else if (!isMarketplaceAPI) {
|
||||
const accessToken = localStorage.getItem('console_token') || ''
|
||||
options.headers.set('Authorization', `Bearer ${accessToken}`)
|
||||
}
|
||||
|
||||
if (deleteContentType) {
|
||||
options.headers.delete('Content-Type')
|
||||
}
|
||||
else {
|
||||
const contentType = options.headers.get('Content-Type')
|
||||
if (!contentType)
|
||||
options.headers.set('Content-Type', ContentType.json)
|
||||
}
|
||||
|
||||
const urlPrefix = (() => {
|
||||
if (isMarketplaceAPI)
|
||||
return MARKETPLACE_API_PREFIX
|
||||
if (isPublicAPI)
|
||||
return PUBLIC_API_PREFIX
|
||||
return API_PREFIX
|
||||
})()
|
||||
let urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`
|
||||
|
||||
const { method, params, body } = options
|
||||
// handle query
|
||||
if (method === 'GET' && params) {
|
||||
const paramsArray: string[] = []
|
||||
Object.keys(params).forEach(key =>
|
||||
paramsArray.push(`${key}=${encodeURIComponent(params[key])}`),
|
||||
)
|
||||
if (urlWithPrefix.search(/\?/) === -1)
|
||||
urlWithPrefix += `?${paramsArray.join('&')}`
|
||||
|
||||
else
|
||||
urlWithPrefix += `&${paramsArray.join('&')}`
|
||||
|
||||
delete options.params
|
||||
}
|
||||
|
||||
if (body && bodyStringify)
|
||||
options.body = JSON.stringify(body)
|
||||
|
||||
// Handle timeout
|
||||
return Promise.race([
|
||||
new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('request timeout'))
|
||||
}, TIME_OUT)
|
||||
}),
|
||||
new Promise((resolve, reject) => {
|
||||
globalThis.fetch(urlWithPrefix, options as RequestInit)
|
||||
.then((res) => {
|
||||
const resClone = res.clone()
|
||||
// Error handler
|
||||
if (!/^(2|3)\d{2}$/.test(String(res.status))) {
|
||||
const bodyJson = res.json()
|
||||
switch (res.status) {
|
||||
case 401:
|
||||
return Promise.reject(resClone)
|
||||
case 403:
|
||||
bodyJson.then((data: ResponseError) => {
|
||||
if (!silent)
|
||||
Toast.notify({ type: 'error', message: data.message })
|
||||
if (data.code === 'already_setup')
|
||||
globalThis.location.href = `${globalThis.location.origin}/signin`
|
||||
})
|
||||
break
|
||||
// fall through
|
||||
default:
|
||||
bodyJson.then((data: ResponseError) => {
|
||||
if (!silent)
|
||||
Toast.notify({ type: 'error', message: data.message })
|
||||
})
|
||||
}
|
||||
return Promise.reject(resClone)
|
||||
}
|
||||
|
||||
// handle delete api. Delete api not return content.
|
||||
if (res.status === 204) {
|
||||
resolve({ result: 'success' })
|
||||
return
|
||||
}
|
||||
|
||||
// return data
|
||||
if (options.headers.get('Content-type') === ContentType.download || options.headers.get('Content-type') === ContentType.audio)
|
||||
resolve(needAllResponseContent ? resClone : res.blob())
|
||||
|
||||
else resolve(needAllResponseContent ? resClone : res.json())
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!silent)
|
||||
Toast.notify({ type: 'error', message: err })
|
||||
reject(err)
|
||||
})
|
||||
}),
|
||||
]) as Promise<T>
|
||||
}
|
||||
const baseFetch = base
|
||||
|
||||
export const upload = (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise<any> => {
|
||||
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
|
||||
let token = ''
|
||||
if (isPublicAPI) {
|
||||
const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0]
|
||||
const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' })
|
||||
let accessTokenJson = { [sharedToken]: '' }
|
||||
try {
|
||||
accessTokenJson = JSON.parse(accessToken)
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
}
|
||||
token = accessTokenJson[sharedToken]
|
||||
token = getPublicToken()
|
||||
}
|
||||
else {
|
||||
const accessToken = localStorage.getItem('console_token') || ''
|
||||
|
@ -499,9 +331,9 @@ export const ssePost = (
|
|||
signal: abortController.signal,
|
||||
}, fetchOptions)
|
||||
|
||||
const contentType = options.headers.get('Content-Type')
|
||||
const contentType = (options.headers as Headers).get('Content-Type')
|
||||
if (!contentType)
|
||||
options.headers.set('Content-Type', ContentType.json)
|
||||
(options.headers as Headers).set('Content-Type', ContentType.json)
|
||||
|
||||
getAbortController?.(abortController)
|
||||
|
||||
|
@ -559,18 +391,17 @@ export const ssePost = (
|
|||
}
|
||||
|
||||
// base request
|
||||
export const request = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
export const request = <T>(url: string, options = {}, otherOptions: IOtherOptions = {}) => {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const otherOptionsForBaseFetch = otherOptions || {}
|
||||
baseFetch<T>(url, options, otherOptionsForBaseFetch).then(resolve).catch((errResp) => {
|
||||
baseFetch<T>(url, options, otherOptions).then(resolve).catch((errResp) => {
|
||||
if (errResp?.status === 401) {
|
||||
return refreshAccessTokenOrRelogin(TIME_OUT).then(() => {
|
||||
baseFetch<T>(url, options, otherOptionsForBaseFetch).then(resolve).catch(reject)
|
||||
baseFetch<T>(url, options, otherOptions).then(resolve).catch(reject)
|
||||
}).catch(() => {
|
||||
const {
|
||||
isPublicAPI = false,
|
||||
silent,
|
||||
} = otherOptionsForBaseFetch
|
||||
} = otherOptions
|
||||
const bodyJson = errResp.json()
|
||||
if (isPublicAPI) {
|
||||
return bodyJson.then((data: ResponseError) => {
|
||||
|
@ -629,6 +460,11 @@ export const post = <T>(url: string, options = {}, otherOptions?: IOtherOptions)
|
|||
return request<T>(url, Object.assign({}, options, { method: 'POST' }), otherOptions)
|
||||
}
|
||||
|
||||
// For Marketplace API
|
||||
export const postMarketplace = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return post<T>(url, options, { ...otherOptions, isMarketplaceAPI: true })
|
||||
}
|
||||
|
||||
export const postPublic = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return post<T>(url, options, { ...otherOptions, isPublicAPI: true })
|
||||
}
|
||||
|
|
187
web/service/fetch.ts
Normal file
187
web/service/fetch.ts
Normal file
|
@ -0,0 +1,187 @@
|
|||
import type { AfterResponseHook, BeforeErrorHook, BeforeRequestHook, Hooks } from 'ky'
|
||||
import ky from 'ky'
|
||||
import type { IOtherOptions } from './base'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { API_PREFIX, MARKETPLACE_API_PREFIX, PUBLIC_API_PREFIX } from '@/config'
|
||||
|
||||
const TIME_OUT = 100000
|
||||
|
||||
export const ContentType = {
|
||||
json: 'application/json',
|
||||
stream: 'text/event-stream',
|
||||
audio: 'audio/mpeg',
|
||||
form: 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
download: 'application/octet-stream', // for download
|
||||
upload: 'multipart/form-data', // for upload
|
||||
}
|
||||
|
||||
export type FetchOptionType = Omit<RequestInit, 'body'> & {
|
||||
params?: Record<string, any>
|
||||
body?: BodyInit | Record<string, any> | null
|
||||
}
|
||||
|
||||
const afterResponse204: AfterResponseHook = async (_request, _options, response) => {
|
||||
if (response.status === 204) return Response.json({ result: 'success' })
|
||||
}
|
||||
|
||||
export type ResponseError = {
|
||||
code: string
|
||||
message: string
|
||||
status: number
|
||||
}
|
||||
|
||||
const afterResponseErrorCode = (otherOptions: IOtherOptions): AfterResponseHook => {
|
||||
return async (_request, _options, response) => {
|
||||
if (!/^(2|3)\d{2}$/.test(String(response.status))) {
|
||||
const bodyJson = response.json() as Promise<ResponseError>
|
||||
switch (response.status) {
|
||||
case 401:
|
||||
return Promise.reject(response)
|
||||
case 403:
|
||||
bodyJson.then((data: ResponseError) => {
|
||||
if (!otherOptions.silent)
|
||||
Toast.notify({ type: 'error', message: data.message })
|
||||
if (data.code === 'already_setup')
|
||||
globalThis.location.href = `${globalThis.location.origin}/signin`
|
||||
})
|
||||
break
|
||||
// fall through
|
||||
default:
|
||||
bodyJson.then((data: ResponseError) => {
|
||||
if (!otherOptions.silent)
|
||||
Toast.notify({ type: 'error', message: data.message })
|
||||
})
|
||||
}
|
||||
throw response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const beforeErrorToast = (otherOptions: IOtherOptions): BeforeErrorHook => {
|
||||
return (error) => {
|
||||
if (!otherOptions.silent)
|
||||
Toast.notify({ type: 'error', message: error.message })
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
export const getPublicToken = () => {
|
||||
let token = ''
|
||||
const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0]
|
||||
const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' })
|
||||
let accessTokenJson = { [sharedToken]: '' }
|
||||
try {
|
||||
accessTokenJson = JSON.parse(accessToken)
|
||||
}
|
||||
catch {}
|
||||
token = accessTokenJson[sharedToken]
|
||||
return token || ''
|
||||
}
|
||||
|
||||
const beforeRequestPublicAuthorization: BeforeRequestHook = (request) => {
|
||||
const token = getPublicToken()
|
||||
request.headers.set('Authorization', `Bearer ${token}`)
|
||||
}
|
||||
|
||||
const beforeRequestAuthorization: BeforeRequestHook = (request) => {
|
||||
const accessToken = localStorage.getItem('console_token') || ''
|
||||
request.headers.set('Authorization', `Bearer ${accessToken}`)
|
||||
}
|
||||
|
||||
const beforeRequestDeleteContentType: BeforeRequestHook = (request) => {
|
||||
request.headers.delete('Content-Type')
|
||||
}
|
||||
|
||||
const baseHooks: Hooks = {
|
||||
afterResponse: [
|
||||
afterResponse204,
|
||||
],
|
||||
}
|
||||
|
||||
const client = ky.create({
|
||||
hooks: baseHooks,
|
||||
timeout: TIME_OUT,
|
||||
})
|
||||
|
||||
export const baseOptions: RequestInit = {
|
||||
method: 'GET',
|
||||
mode: 'cors',
|
||||
credentials: 'include', // always send cookies、HTTP Basic authentication.
|
||||
headers: new Headers({
|
||||
'Content-Type': ContentType.json,
|
||||
}),
|
||||
redirect: 'follow',
|
||||
}
|
||||
|
||||
async function base<T>(url: string, options: FetchOptionType = {}, otherOptions: IOtherOptions = {}): Promise<T> {
|
||||
const { params, body, ...init } = Object.assign({}, baseOptions, options)
|
||||
const {
|
||||
isPublicAPI = false,
|
||||
isMarketplaceAPI = false,
|
||||
bodyStringify = true,
|
||||
needAllResponseContent,
|
||||
deleteContentType,
|
||||
getAbortController,
|
||||
} = otherOptions
|
||||
|
||||
const base
|
||||
= isMarketplaceAPI
|
||||
? MARKETPLACE_API_PREFIX
|
||||
: isPublicAPI
|
||||
? PUBLIC_API_PREFIX
|
||||
: API_PREFIX
|
||||
|
||||
if (getAbortController) {
|
||||
const abortController = new AbortController()
|
||||
getAbortController(abortController)
|
||||
options.signal = abortController.signal
|
||||
}
|
||||
|
||||
const fetchPathname = `${base}${url.startsWith('/') ? url : `/${url}`}`
|
||||
|
||||
const res = await client.extend({
|
||||
hooks: {
|
||||
...baseHooks,
|
||||
beforeError: [
|
||||
...baseHooks.beforeError || [],
|
||||
beforeErrorToast(otherOptions),
|
||||
],
|
||||
beforeRequest: [
|
||||
...baseHooks.beforeRequest || [],
|
||||
isPublicAPI && beforeRequestPublicAuthorization,
|
||||
!isPublicAPI && !isMarketplaceAPI && beforeRequestAuthorization,
|
||||
deleteContentType && beforeRequestDeleteContentType,
|
||||
].filter(i => !!i),
|
||||
afterResponse: [
|
||||
...baseHooks.afterResponse || [],
|
||||
afterResponseErrorCode(otherOptions),
|
||||
],
|
||||
},
|
||||
})(fetchPathname, {
|
||||
...init,
|
||||
credentials: isMarketplaceAPI
|
||||
? 'omit'
|
||||
: (options.credentials || 'include'),
|
||||
retry: {
|
||||
methods: [],
|
||||
},
|
||||
...(bodyStringify ? { json: body } : { body: body as BodyInit }),
|
||||
searchParams: params,
|
||||
})
|
||||
|
||||
if (needAllResponseContent)
|
||||
return res as T
|
||||
const contentType = res.headers.get('content-type')
|
||||
if (
|
||||
contentType
|
||||
&& [ContentType.download, ContentType.audio].includes(contentType)
|
||||
)
|
||||
return await res.blob() as T
|
||||
|
||||
return await res.json() as T
|
||||
}
|
||||
|
||||
export {
|
||||
client,
|
||||
base,
|
||||
}
|
|
@ -1,10 +1,6 @@
|
|||
import type { Fetcher } from 'swr'
|
||||
import { del, get, getMarketplace, post, upload } from './base'
|
||||
import { get, getMarketplace, post, upload } from './base'
|
||||
import type {
|
||||
CreateEndpointRequest,
|
||||
EndpointOperationResponse,
|
||||
EndpointsRequest,
|
||||
EndpointsResponse,
|
||||
InstallPackageResponse,
|
||||
Permissions,
|
||||
PluginDeclaration,
|
||||
|
@ -12,49 +8,13 @@ import type {
|
|||
PluginTasksResponse,
|
||||
TaskStatusResponse,
|
||||
UninstallPluginResponse,
|
||||
UpdateEndpointRequest,
|
||||
uploadGitHubResponse,
|
||||
} from '@/app/components/plugins/types'
|
||||
import type { DebugInfo as DebugInfoTypes } from '@/app/components/plugins/types'
|
||||
import type {
|
||||
MarketplaceCollectionPluginsResponse,
|
||||
MarketplaceCollectionsResponse,
|
||||
} from '@/app/components/plugins/marketplace/types'
|
||||
|
||||
export const createEndpoint: Fetcher<EndpointOperationResponse, { url: string; body: CreateEndpointRequest }> = ({ url, body }) => {
|
||||
// url = /workspaces/current/endpoints/create
|
||||
return post<EndpointOperationResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const fetchEndpointList: Fetcher<EndpointsResponse, { url: string; params?: EndpointsRequest }> = ({ url, params }) => {
|
||||
// url = /workspaces/current/endpoints/list/plugin?plugin_id=xxx
|
||||
return get<EndpointsResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const deleteEndpoint: Fetcher<EndpointOperationResponse, { url: string; endpointID: string }> = ({ url, endpointID }) => {
|
||||
// url = /workspaces/current/endpoints/delete
|
||||
return del<EndpointOperationResponse>(url, { body: { endpoint_id: endpointID } })
|
||||
}
|
||||
|
||||
export const updateEndpoint: Fetcher<EndpointOperationResponse, { url: string; body: UpdateEndpointRequest }> = ({ url, body }) => {
|
||||
// url = /workspaces/current/endpoints/update
|
||||
return post<EndpointOperationResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const enableEndpoint: Fetcher<EndpointOperationResponse, { url: string; endpointID: string }> = ({ url, endpointID }) => {
|
||||
// url = /workspaces/current/endpoints/enable
|
||||
return post<EndpointOperationResponse>(url, { body: { endpoint_id: endpointID } })
|
||||
}
|
||||
|
||||
export const disableEndpoint: Fetcher<EndpointOperationResponse, { url: string; endpointID: string }> = ({ url, endpointID }) => {
|
||||
// url = /workspaces/current/endpoints/disable
|
||||
return post<EndpointOperationResponse>(url, { body: { endpoint_id: endpointID } })
|
||||
}
|
||||
|
||||
export const fetchDebugKey = async () => {
|
||||
return get<DebugInfoTypes>('/workspaces/current/plugin/debugging-key')
|
||||
}
|
||||
|
||||
export const uploadPackageFile = async (file: File) => {
|
||||
const formData = new FormData()
|
||||
formData.append('pkg', file)
|
||||
|
@ -64,12 +24,6 @@ export const uploadPackageFile = async (file: File) => {
|
|||
}, false, '/workspaces/current/plugin/upload/pkg')
|
||||
}
|
||||
|
||||
export const installPackageFromLocal = async (uniqueIdentifier: string) => {
|
||||
return post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
|
||||
body: { plugin_unique_identifiers: [uniqueIdentifier] },
|
||||
})
|
||||
}
|
||||
|
||||
export const updateFromMarketPlace = async (body: Record<string, string>) => {
|
||||
return post<InstallPackageResponse>('/workspaces/current/plugin/upgrade/marketplace', {
|
||||
body,
|
||||
|
@ -109,12 +63,6 @@ export const fetchManifestFromMarketPlace = async (uniqueIdentifier: string) =>
|
|||
return getMarketplace<{ data: { plugin: PluginManifestInMarket } }>(`/plugins/identifier?unique_identifier=${uniqueIdentifier}`)
|
||||
}
|
||||
|
||||
export const installPackageFromMarketPlace = async (uniqueIdentifier: string) => {
|
||||
return post<InstallPackageResponse>('/workspaces/current/plugin/install/marketplace', {
|
||||
body: { plugin_unique_identifiers: [uniqueIdentifier] },
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchMarketplaceCollections: Fetcher<MarketplaceCollectionsResponse, { url: string; }> = ({ url }) => {
|
||||
return get<MarketplaceCollectionsResponse>(url)
|
||||
}
|
||||
|
@ -131,10 +79,6 @@ export const checkTaskStatus = async (taskId: string) => {
|
|||
return get<TaskStatusResponse>(`/workspaces/current/plugin/tasks/${taskId}`)
|
||||
}
|
||||
|
||||
export const fetchPermission = async () => {
|
||||
return get<Permissions>('/workspaces/current/plugin/permission/fetch')
|
||||
}
|
||||
|
||||
export const updatePermission = async (permissions: Permissions) => {
|
||||
return post('/workspaces/current/plugin/permission/change', { body: permissions })
|
||||
}
|
||||
|
|
149
web/service/use-endpoints.ts
Normal file
149
web/service/use-endpoints.ts
Normal file
|
@ -0,0 +1,149 @@
|
|||
import { get, post } from './base'
|
||||
import type {
|
||||
EndpointsResponse,
|
||||
} from '@/app/components/plugins/types'
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
const NAME_SPACE = 'endpoints'
|
||||
|
||||
export const useEndpointList = (pluginID: string) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'list', pluginID],
|
||||
queryFn: () => get<EndpointsResponse>('/workspaces/current/endpoints/list/plugin', {
|
||||
params: {
|
||||
plugin_id: pluginID,
|
||||
page: 1,
|
||||
page_size: 100,
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateEndpointList = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (pluginID: string) => {
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: [NAME_SPACE, 'list', pluginID],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const useCreateEndpoint = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'create'],
|
||||
mutationFn: (payload: { pluginUniqueID: string, state: Record<string, any> }) => {
|
||||
const { pluginUniqueID, state } = payload
|
||||
const newName = state.name
|
||||
delete state.name
|
||||
return post('/workspaces/current/endpoints/create', {
|
||||
body: {
|
||||
plugin_unique_identifier: pluginUniqueID,
|
||||
settings: state,
|
||||
name: newName,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
onError,
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateEndpoint = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update'],
|
||||
mutationFn: (payload: { endpointID: string, state: Record<string, any> }) => {
|
||||
const { endpointID, state } = payload
|
||||
const newName = state.name
|
||||
delete state.name
|
||||
return post('/workspaces/current/endpoints/update', {
|
||||
body: {
|
||||
endpoint_id: endpointID,
|
||||
settings: state,
|
||||
name: newName,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
onError,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteEndpoint = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'delete'],
|
||||
mutationFn: (endpointID: string) => {
|
||||
return post('/workspaces/current/endpoints/delete', {
|
||||
body: {
|
||||
endpoint_id: endpointID,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
onError,
|
||||
})
|
||||
}
|
||||
|
||||
export const useEnableEndpoint = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'enable'],
|
||||
mutationFn: (endpointID: string) => {
|
||||
return post('/workspaces/current/endpoints/enable', {
|
||||
body: {
|
||||
endpoint_id: endpointID,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
onError,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDisableEndpoint = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'disable'],
|
||||
mutationFn: (endpointID: string) => {
|
||||
return post('/workspaces/current/endpoints/disable', {
|
||||
body: {
|
||||
endpoint_id: endpointID,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
onError,
|
||||
})
|
||||
}
|
17
web/service/use-models.ts
Normal file
17
web/service/use-models.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { get } from './base'
|
||||
import type {
|
||||
ModelItem,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import {
|
||||
useQuery,
|
||||
// useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
const NAME_SPACE = 'models'
|
||||
|
||||
export const useModelProviderModelList = (provider: string) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'model-list', provider],
|
||||
queryFn: () => get<{ data: ModelItem[] }>(`/workspaces/current/model-providers/${provider}/models`),
|
||||
})
|
||||
}
|
|
@ -1,11 +1,18 @@
|
|||
import type { DebugInfo as DebugInfoTypes, InstalledPluginListResponse } from '@/app/components/plugins/types'
|
||||
import { get } from './base'
|
||||
import {
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import type {
|
||||
DebugInfo as DebugInfoTypes,
|
||||
InstallPackageResponse,
|
||||
InstalledPluginListResponse,
|
||||
Permissions,
|
||||
PluginsFromMarketplaceResponse,
|
||||
} from '@/app/components/plugins/types'
|
||||
import type {
|
||||
PluginsSearchParams,
|
||||
} from '@/app/components/plugins/marketplace/types'
|
||||
import { get, post, postMarketplace } from './base'
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
const NAME_SPACE = 'plugins'
|
||||
|
@ -28,9 +35,83 @@ export const useInvalidateInstalledPluginList = () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const useInstallPackageFromMarketPlace = () => {
|
||||
return useMutation({
|
||||
mutationFn: (uniqueIdentifier: string) => {
|
||||
return post<InstallPackageResponse>('/workspaces/current/plugin/install/marketplace', { body: { plugin_unique_identifiers: [uniqueIdentifier] } })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useInstallPackageFromLocal = () => {
|
||||
return useMutation({
|
||||
mutationFn: (uniqueIdentifier: string) => {
|
||||
return post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
|
||||
body: { plugin_unique_identifiers: [uniqueIdentifier] },
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
} = pluginsSearchParams
|
||||
return postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/search/basic', {
|
||||
body: {
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
query,
|
||||
sort_by: sortBy,
|
||||
sort_order: sortOrder,
|
||||
category: category !== 'all' ? category : '',
|
||||
tags,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { get } from './base'
|
||||
import { get, post } from './base'
|
||||
import type {
|
||||
Collection,
|
||||
Tool,
|
||||
} from '@/app/components/tools/types'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import {
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
const NAME_SPACE = 'tools'
|
||||
|
@ -45,9 +44,61 @@ export const useAllWorkflowTools = () => {
|
|||
})
|
||||
}
|
||||
|
||||
export const useBuiltInTools = (collectionName: string) => {
|
||||
export const useBuiltinProviderInfo = (providerName: string) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'builtIn', collectionName],
|
||||
queryFn: () => get<Tool[]>(`/workspaces/current/tool-provider/builtin/${collectionName}/tools`),
|
||||
queryKey: [NAME_SPACE, 'builtin-provider-info', providerName],
|
||||
queryFn: () => get<Collection>(`/workspaces/current/tool-provider/builtin/${providerName}/info`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateBuiltinProviderInfo = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (providerName: string) => {
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: [NAME_SPACE, 'builtin-provider-info', providerName],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const useBuiltinTools = (providerName: string) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'builtin-provider-tools', providerName],
|
||||
queryFn: () => get<Tool[]>(`/workspaces/current/tool-provider/builtin/${providerName}/tools`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateProviderCredentials = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update-provider-credentials'],
|
||||
mutationFn: (payload: { providerName: string, credentials: Record<string, any> }) => {
|
||||
const { providerName, credentials } = payload
|
||||
return post(`/workspaces/current/tool-provider/builtin/${providerName}/update`, {
|
||||
body: {
|
||||
credentials,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
})
|
||||
}
|
||||
|
||||
export const useRemoveProviderCredentials = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'remove-provider-credentials'],
|
||||
mutationFn: (providerName: string) => {
|
||||
return post(`/workspaces/current/tool-provider/builtin/${providerName}/delete`, {
|
||||
body: {},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user