mirror of
https://github.com/langgenius/dify.git
synced 2026-05-13 08:57:28 +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} />
|
<GA gaType={GaType.admin} />
|
||||||
<SwrInitor>
|
<SwrInitor>
|
||||||
<AppContextProvider>
|
<TanstackQueryIniter>
|
||||||
<EventEmitterContextProvider>
|
<AppContextProvider>
|
||||||
<ProviderContextProvider>
|
<EventEmitterContextProvider>
|
||||||
<ModalContextProvider>
|
<ProviderContextProvider>
|
||||||
<HeaderWrapper>
|
<ModalContextProvider>
|
||||||
<Header />
|
<HeaderWrapper>
|
||||||
</HeaderWrapper>
|
<Header />
|
||||||
<TanstackQueryIniter>
|
</HeaderWrapper>
|
||||||
{children}
|
{children}
|
||||||
</TanstackQueryIniter>
|
</ModalContextProvider>
|
||||||
</ModalContextProvider>
|
</ProviderContextProvider>
|
||||||
</ProviderContextProvider>
|
</EventEmitterContextProvider>
|
||||||
</EventEmitterContextProvider>
|
</AppContextProvider>
|
||||||
</AppContextProvider>
|
</TanstackQueryIniter>
|
||||||
</SwrInitor>
|
</SwrInitor>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -222,7 +222,7 @@ const ModelProviderPage = ({ searchText }: Props) => {
|
|||||||
{!collapse && !isPluginsLoading && (
|
{!collapse && !isPluginsLoading && (
|
||||||
<div className='grid grid-cols-2 gap-2'>
|
<div className='grid grid-cols-2 gap-2'>
|
||||||
{plugins.map(plugin => (
|
{plugins.map(plugin => (
|
||||||
<ProviderCard key={plugin.plugin_id} payload={plugin} />
|
<ProviderCard key={plugin.plugin_id} payload={plugin} onSuccess={updateModelProviders} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -50,15 +50,15 @@ const ModelList: FC<ModelListProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='px-2 pb-2 rounded-b-xl'>
|
<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]'>
|
<div className='flex items-center pl-1 pr-[3px]'>
|
||||||
<span className='group shrink-0 flex items-center mr-2'>
|
<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 })}
|
{t('common.modelProvider.modelsNum', { num: models.length })}
|
||||||
<RiArrowRightSLine className='mr-0.5 w-4 h-4 rotate-90' />
|
<RiArrowRightSLine className='mr-0.5 w-4 h-4 rotate-90' />
|
||||||
</span>
|
</span>
|
||||||
<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()}
|
onClick={() => onCollapse()}
|
||||||
>
|
>
|
||||||
{t('common.modelProvider.modelsNum', { num: models.length })}
|
{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 { Trans, useTranslation } from 'react-i18next'
|
||||||
import { RiLoader2Line } from '@remixicon/react'
|
import { RiLoader2Line } from '@remixicon/react'
|
||||||
import Badge, { BadgeState } from '@/app/components/base/badge/index'
|
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 checkTaskStatus from '../../base/check-task-status'
|
||||||
import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store'
|
import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store'
|
||||||
|
|
||||||
@ -33,6 +33,8 @@ const Installed: FC<Props> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [isInstalling, setIsInstalling] = React.useState(false)
|
const [isInstalling, setIsInstalling] = React.useState(false)
|
||||||
|
const { mutateAsync: installPackageFromLocal } = useInstallPackageFromLocal()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
check,
|
check,
|
||||||
stop,
|
stop,
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import Button from '@/app/components/base/button'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { RiLoader2Line } from '@remixicon/react'
|
import { RiLoader2Line } from '@remixicon/react'
|
||||||
import Badge, { BadgeState } from '@/app/components/base/badge/index'
|
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'
|
import checkTaskStatus from '../../base/check-task-status'
|
||||||
|
|
||||||
const i18nPrefix = 'plugin.installModal'
|
const i18nPrefix = 'plugin.installModal'
|
||||||
@ -32,6 +32,7 @@ const Installed: FC<Props> = ({
|
|||||||
onFailed,
|
onFailed,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { mutateAsync: installPackageFromMarketPlace } = useInstallPackageFromMarketPlace()
|
||||||
const [isInstalling, setIsInstalling] = React.useState(false)
|
const [isInstalling, setIsInstalling] = React.useState(false)
|
||||||
const {
|
const {
|
||||||
check,
|
check,
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export type MarketplaceContextValue = {
|
|||||||
activePluginType: string
|
activePluginType: string
|
||||||
handleActivePluginTypeChange: (type: string) => void
|
handleActivePluginTypeChange: (type: string) => void
|
||||||
plugins?: Plugin[]
|
plugins?: Plugin[]
|
||||||
setPlugins: (plugins: Plugin[]) => void
|
resetPlugins: () => void
|
||||||
sort: PluginsSort
|
sort: PluginsSort
|
||||||
handleSortChange: (sort: PluginsSort) => void
|
handleSortChange: (sort: PluginsSort) => void
|
||||||
marketplaceCollectionsFromClient?: MarketplaceCollection[]
|
marketplaceCollectionsFromClient?: MarketplaceCollection[]
|
||||||
@ -53,7 +53,7 @@ export const MarketplaceContext = createContext<MarketplaceContextValue>({
|
|||||||
activePluginType: PLUGIN_TYPE_SEARCH_MAP.all,
|
activePluginType: PLUGIN_TYPE_SEARCH_MAP.all,
|
||||||
handleActivePluginTypeChange: () => {},
|
handleActivePluginTypeChange: () => {},
|
||||||
plugins: undefined,
|
plugins: undefined,
|
||||||
setPlugins: () => {},
|
resetPlugins: () => {},
|
||||||
sort: DEFAULT_SORT,
|
sort: DEFAULT_SORT,
|
||||||
handleSortChange: () => {},
|
handleSortChange: () => {},
|
||||||
marketplaceCollectionsFromClient: [],
|
marketplaceCollectionsFromClient: [],
|
||||||
@ -91,7 +91,7 @@ export const MarketplaceContextProvider = ({
|
|||||||
} = useMarketplaceCollectionsAndPlugins()
|
} = useMarketplaceCollectionsAndPlugins()
|
||||||
const {
|
const {
|
||||||
plugins,
|
plugins,
|
||||||
setPlugins,
|
resetPlugins,
|
||||||
queryPlugins,
|
queryPlugins,
|
||||||
queryPluginsWithDebounced,
|
queryPluginsWithDebounced,
|
||||||
} = useMarketplacePlugins()
|
} = useMarketplacePlugins()
|
||||||
@ -104,7 +104,7 @@ export const MarketplaceContextProvider = ({
|
|||||||
queryMarketplaceCollectionsAndPlugins({
|
queryMarketplaceCollectionsAndPlugins({
|
||||||
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
|
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
|
||||||
})
|
})
|
||||||
setPlugins(undefined)
|
resetPlugins()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -116,7 +116,7 @@ export const MarketplaceContextProvider = ({
|
|||||||
sortBy: sortRef.current.sortBy,
|
sortBy: sortRef.current.sortBy,
|
||||||
sortOrder: sortRef.current.sortOrder,
|
sortOrder: sortRef.current.sortOrder,
|
||||||
})
|
})
|
||||||
}, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, setPlugins])
|
}, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, resetPlugins])
|
||||||
|
|
||||||
const handleFilterPluginTagsChange = useCallback((tags: string[]) => {
|
const handleFilterPluginTagsChange = useCallback((tags: string[]) => {
|
||||||
setFilterPluginTags(tags)
|
setFilterPluginTags(tags)
|
||||||
@ -126,7 +126,7 @@ export const MarketplaceContextProvider = ({
|
|||||||
queryMarketplaceCollectionsAndPlugins({
|
queryMarketplaceCollectionsAndPlugins({
|
||||||
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
|
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
|
||||||
})
|
})
|
||||||
setPlugins(undefined)
|
resetPlugins()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -138,7 +138,7 @@ export const MarketplaceContextProvider = ({
|
|||||||
sortBy: sortRef.current.sortBy,
|
sortBy: sortRef.current.sortBy,
|
||||||
sortOrder: sortRef.current.sortOrder,
|
sortOrder: sortRef.current.sortOrder,
|
||||||
})
|
})
|
||||||
}, [queryPlugins, setPlugins, queryMarketplaceCollectionsAndPlugins])
|
}, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins])
|
||||||
|
|
||||||
const handleActivePluginTypeChange = useCallback((type: string) => {
|
const handleActivePluginTypeChange = useCallback((type: string) => {
|
||||||
setActivePluginType(type)
|
setActivePluginType(type)
|
||||||
@ -148,7 +148,7 @@ export const MarketplaceContextProvider = ({
|
|||||||
queryMarketplaceCollectionsAndPlugins({
|
queryMarketplaceCollectionsAndPlugins({
|
||||||
category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type,
|
category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type,
|
||||||
})
|
})
|
||||||
setPlugins(undefined)
|
resetPlugins()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -160,7 +160,7 @@ export const MarketplaceContextProvider = ({
|
|||||||
sortBy: sortRef.current.sortBy,
|
sortBy: sortRef.current.sortBy,
|
||||||
sortOrder: sortRef.current.sortOrder,
|
sortOrder: sortRef.current.sortOrder,
|
||||||
})
|
})
|
||||||
}, [queryPlugins, setPlugins, queryMarketplaceCollectionsAndPlugins])
|
}, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins])
|
||||||
|
|
||||||
const handleSortChange = useCallback((sort: PluginsSort) => {
|
const handleSortChange = useCallback((sort: PluginsSort) => {
|
||||||
setSort(sort)
|
setSort(sort)
|
||||||
@ -187,7 +187,7 @@ export const MarketplaceContextProvider = ({
|
|||||||
activePluginType,
|
activePluginType,
|
||||||
handleActivePluginTypeChange,
|
handleActivePluginTypeChange,
|
||||||
plugins,
|
plugins,
|
||||||
setPlugins,
|
resetPlugins,
|
||||||
sort,
|
sort,
|
||||||
handleSortChange,
|
handleSortChange,
|
||||||
marketplaceCollectionsFromClient,
|
marketplaceCollectionsFromClient,
|
||||||
|
|||||||
@ -15,10 +15,10 @@ const Description = async ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className='shrink-0 mb-2 text-center title-4xl-semi-bold text-text-primary'>
|
<h1 className='shrink-0 mb-2 text-center title-4xl-semi-bold text-text-primary'>
|
||||||
Empower your AI development
|
{t('marketplace.empower')}
|
||||||
</h1>
|
</h1>
|
||||||
<h2 className='shrink-0 flex justify-center items-center text-center body-md-regular text-text-tertiary'>
|
<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">
|
<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')}
|
{t('category.models')}
|
||||||
</span>
|
</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">
|
<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')}
|
{t('category.extensions')}
|
||||||
</span>
|
</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">
|
<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')}
|
{t('category.bundles')}
|
||||||
</span>
|
</span>
|
||||||
in Dify Marketplace
|
{t('marketplace.inDifyMarketplace')}
|
||||||
</h2>
|
</h2>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useDebounceFn } from 'ahooks'
|
import { useDebounceFn } from 'ahooks'
|
||||||
import type { Plugin } from '../types'
|
import type {
|
||||||
|
Plugin,
|
||||||
|
} from '../types'
|
||||||
import type {
|
import type {
|
||||||
CollectionsAndPluginsSearchParams,
|
CollectionsAndPluginsSearchParams,
|
||||||
MarketplaceCollection,
|
MarketplaceCollection,
|
||||||
@ -12,9 +14,9 @@ import type {
|
|||||||
} from './types'
|
} from './types'
|
||||||
import {
|
import {
|
||||||
getMarketplaceCollectionsAndPlugins,
|
getMarketplaceCollectionsAndPlugins,
|
||||||
getMarketplacePlugins,
|
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import i18n from '@/i18n/i18next-config'
|
import i18n from '@/i18n/i18next-config'
|
||||||
|
import { useMutationPluginsFromMarketplace } from '@/service/use-plugins'
|
||||||
|
|
||||||
export const useMarketplaceCollectionsAndPlugins = () => {
|
export const useMarketplaceCollectionsAndPlugins = () => {
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
@ -41,28 +43,29 @@ export const useMarketplaceCollectionsAndPlugins = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useMarketplacePlugins = () => {
|
export const useMarketplacePlugins = () => {
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const {
|
||||||
const [plugins, setPlugins] = useState<Plugin[]>()
|
data,
|
||||||
|
mutate,
|
||||||
|
reset,
|
||||||
|
isPending,
|
||||||
|
} = useMutationPluginsFromMarketplace()
|
||||||
|
|
||||||
const queryPlugins = useCallback(async (query: PluginsSearchParams) => {
|
const queryPlugins = useCallback((pluginsSearchParams: PluginsSearchParams) => {
|
||||||
setIsLoading(true)
|
mutate(pluginsSearchParams)
|
||||||
const { marketplacePlugins } = await getMarketplacePlugins(query)
|
}, [mutate])
|
||||||
setIsLoading(false)
|
|
||||||
|
|
||||||
setPlugins(marketplacePlugins)
|
const { run: queryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams) => {
|
||||||
}, [])
|
mutate(pluginsSearchParams)
|
||||||
|
}, {
|
||||||
const { run: queryPluginsWithDebounced } = useDebounceFn(queryPlugins, {
|
|
||||||
wait: 500,
|
wait: 500,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins,
|
plugins: data?.data?.plugins,
|
||||||
setPlugins,
|
resetPlugins: reset,
|
||||||
queryPlugins,
|
queryPlugins,
|
||||||
queryPluginsWithDebounced,
|
queryPluginsWithDebounced,
|
||||||
isLoading,
|
isLoading: isPending,
|
||||||
setIsLoading,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import SearchBoxWrapper from './search-box/search-box-wrapper'
|
|||||||
import PluginTypeSwitch from './plugin-type-switch'
|
import PluginTypeSwitch from './plugin-type-switch'
|
||||||
import ListWrapper from './list/list-wrapper'
|
import ListWrapper from './list/list-wrapper'
|
||||||
import { getMarketplaceCollectionsAndPlugins } from './utils'
|
import { getMarketplaceCollectionsAndPlugins } from './utils'
|
||||||
|
import { TanstackQueryIniter } from '@/context/query-client'
|
||||||
|
|
||||||
type MarketplaceProps = {
|
type MarketplaceProps = {
|
||||||
locale?: string
|
locale?: string
|
||||||
@ -17,18 +18,20 @@ const Marketplace = async ({
|
|||||||
const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins()
|
const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MarketplaceContextProvider>
|
<TanstackQueryIniter>
|
||||||
<Description locale={locale} />
|
<MarketplaceContextProvider>
|
||||||
<IntersectionLine />
|
<Description locale={locale} />
|
||||||
<SearchBoxWrapper locale={locale} />
|
<IntersectionLine />
|
||||||
<PluginTypeSwitch locale={locale} />
|
<SearchBoxWrapper locale={locale} />
|
||||||
<ListWrapper
|
<PluginTypeSwitch locale={locale} />
|
||||||
locale={locale}
|
<ListWrapper
|
||||||
marketplaceCollections={marketplaceCollections}
|
locale={locale}
|
||||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
|
marketplaceCollections={marketplaceCollections}
|
||||||
showInstallButton={showInstallButton}
|
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
|
||||||
/>
|
showInstallButton={showInstallButton}
|
||||||
</MarketplaceContextProvider>
|
/>
|
||||||
|
</MarketplaceContextProvider>
|
||||||
|
</TanstackQueryIniter>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const PluginTypeSwitch = ({
|
|||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
value: PLUGIN_TYPE_SEARCH_MAP.all,
|
value: PLUGIN_TYPE_SEARCH_MAP.all,
|
||||||
text: 'All',
|
text: t('plugin.category.all'),
|
||||||
icon: null,
|
icon: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -34,7 +34,7 @@ const PluginSettingModal: FC<Props> = ({
|
|||||||
const handleSave = useCallback(async () => {
|
const handleSave = useCallback(async () => {
|
||||||
await onSave(tempPrivilege)
|
await onSave(tempPrivilege)
|
||||||
onHide()
|
onHide()
|
||||||
}, [tempPrivilege])
|
}, [onHide, onSave, tempPrivilege])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import useSWR from 'swr'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
|
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
|
||||||
import { useAppContext } from '@/context/app-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 ToolItem from '@/app/components/tools/provider/tool-item'
|
||||||
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
|
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
|
||||||
import {
|
import {
|
||||||
fetchBuiltInToolList,
|
useBuiltinProviderInfo,
|
||||||
fetchCollectionDetail,
|
useBuiltinTools,
|
||||||
removeBuiltInToolCredential,
|
useInvalidateBuiltinProviderInfo,
|
||||||
updateBuiltInToolCredential,
|
useRemoveProviderCredentials,
|
||||||
} from '@/service/tools'
|
useUpdateProviderCredentials,
|
||||||
|
} from '@/service/use-tools'
|
||||||
|
|
||||||
const ActionList = () => {
|
const ActionList = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { isCurrentWorkspaceManager } = useAppContext()
|
const { isCurrentWorkspaceManager } = useAppContext()
|
||||||
const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
||||||
const { data: provider } = useSWR(
|
const { data: provider } = useBuiltinProviderInfo(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
|
||||||
`builtin/${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`,
|
const invalidateProviderInfo = useInvalidateBuiltinProviderInfo()
|
||||||
fetchCollectionDetail,
|
const { data } = useBuiltinTools(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
|
||||||
)
|
|
||||||
const { data } = useSWR(
|
|
||||||
`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`,
|
|
||||||
fetchBuiltInToolList,
|
|
||||||
)
|
|
||||||
|
|
||||||
const [showSettingAuth, setShowSettingAuth] = useState(false)
|
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)
|
if (!data || !provider)
|
||||||
return null
|
return null
|
||||||
@ -77,24 +87,11 @@ const ActionList = () => {
|
|||||||
<ConfigCredential
|
<ConfigCredential
|
||||||
collection={provider}
|
collection={provider}
|
||||||
onCancel={() => setShowSettingAuth(false)}
|
onCancel={() => setShowSettingAuth(false)}
|
||||||
onSaved={async (value) => {
|
onSaved={async value => updatePermission({
|
||||||
await updateBuiltInToolCredential(provider.name, value)
|
providerName: provider.name,
|
||||||
Toast.notify({
|
credentials: value,
|
||||||
type: 'success',
|
})}
|
||||||
message: t('common.api.actionSuccess'),
|
onRemove={async () => removePermission(provider.name)}
|
||||||
})
|
|
||||||
handleCredentialSettingUpdate()
|
|
||||||
setShowSettingAuth(false)
|
|
||||||
}}
|
|
||||||
onRemove={async () => {
|
|
||||||
await removeBuiltInToolCredential(provider.name)
|
|
||||||
Toast.notify({
|
|
||||||
type: 'success',
|
|
||||||
message: t('common.api.actionSuccess'),
|
|
||||||
})
|
|
||||||
handleCredentialSettingUpdate()
|
|
||||||
setShowSettingAuth(false)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import Description from '../card/base/description'
|
|||||||
import Icon from '../card/base/card-icon'
|
import Icon from '../card/base/card-icon'
|
||||||
import Title from '../card/base/title'
|
import Title from '../card/base/title'
|
||||||
import OrgInfo from '../card/base/org-info'
|
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 OperationDropdown from './operation-dropdown'
|
||||||
import PluginInfo from '@/app/components/plugins/plugin-page/plugin-info'
|
import PluginInfo from '@/app/components/plugins/plugin-page/plugin-info'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
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 Badge from '@/app/components/base/badge'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
import Confirm from '@/app/components/base/confirm'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
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 { BoxSparkleFill } from '@/app/components/base/icons/src/vender/plugin'
|
||||||
import { Github } from '@/app/components/base/icons/src/public/common'
|
import { Github } from '@/app/components/base/icons/src/public/common'
|
||||||
import { uninstallPlugin } from '@/service/plugins'
|
import { uninstallPlugin } from '@/service/plugins'
|
||||||
import { useGetLanguage } from '@/context/i18n'
|
import { useGetLanguage } from '@/context/i18n'
|
||||||
|
import { useModalContext } from '@/context/modal-context'
|
||||||
|
|
||||||
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
@ -32,16 +37,18 @@ const i18nPrefix = 'plugin.action'
|
|||||||
type Props = {
|
type Props = {
|
||||||
detail: PluginDetail
|
detail: PluginDetail
|
||||||
onHide: () => void
|
onHide: () => void
|
||||||
onDelete: () => void
|
onUpdate: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const DetailHeader = ({
|
const DetailHeader = ({
|
||||||
detail,
|
detail,
|
||||||
onHide,
|
onHide,
|
||||||
onDelete,
|
onUpdate,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const locale = useGetLanguage()
|
const locale = useGetLanguage()
|
||||||
|
const { fetchReleases } = useGitHubReleases()
|
||||||
|
const { setShowUpdatePluginModal } = useModalContext()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
installation_id,
|
installation_id,
|
||||||
@ -53,13 +60,51 @@ const DetailHeader = ({
|
|||||||
} = detail
|
} = detail
|
||||||
const { author, name, label, description, icon, verified } = detail.declaration
|
const { author, name, label, description, icon, verified } = detail.declaration
|
||||||
const isFromGitHub = source === PluginSource.github
|
const isFromGitHub = source === PluginSource.github
|
||||||
// Only plugin installed from GitHub need to check if it's the new version
|
|
||||||
const hasNewVersion = useMemo(() => {
|
const hasNewVersion = useMemo(() => {
|
||||||
return source === PluginSource.github && latest_version !== version
|
return source === PluginSource.github && latest_version !== version
|
||||||
}, [source, latest_version, version])
|
}, [source, latest_version, version])
|
||||||
|
|
||||||
// #plugin TODO# update plugin
|
const handleUpdate = async () => {
|
||||||
const handleUpdate = () => { }
|
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, {
|
const [isShowPluginInfo, {
|
||||||
setTrue: showPluginInfo,
|
setTrue: showPluginInfo,
|
||||||
@ -82,9 +127,9 @@ const DetailHeader = ({
|
|||||||
hideDeleting()
|
hideDeleting()
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
hideDeleteConfirm()
|
hideDeleteConfirm()
|
||||||
onDelete()
|
onUpdate()
|
||||||
}
|
}
|
||||||
}, [hideDeleteConfirm, hideDeleting, installation_id, showDeleting, onDelete])
|
}, [hideDeleteConfirm, hideDeleting, installation_id, showDeleting, onUpdate])
|
||||||
|
|
||||||
// #plugin TODO# used in apps
|
// #plugin TODO# used in apps
|
||||||
// const usedInApps = 3
|
// const usedInApps = 3
|
||||||
@ -141,6 +186,7 @@ const DetailHeader = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className='flex gap-1'>
|
<div className='flex gap-1'>
|
||||||
<OperationDropdown
|
<OperationDropdown
|
||||||
|
source={detail.source}
|
||||||
onInfo={showPluginInfo}
|
onInfo={showPluginInfo}
|
||||||
onCheckVersion={handleUpdate}
|
onCheckVersion={handleUpdate}
|
||||||
onRemove={showDeleteConfirm}
|
onRemove={showDeleteConfirm}
|
||||||
|
|||||||
@ -4,62 +4,62 @@ import { useBoolean } from 'ahooks'
|
|||||||
import { RiDeleteBinLine, RiEditLine, RiLoginCircleLine } from '@remixicon/react'
|
import { RiDeleteBinLine, RiEditLine, RiLoginCircleLine } from '@remixicon/react'
|
||||||
import type { EndpointListItem } from '../types'
|
import type { EndpointListItem } from '../types'
|
||||||
import EndpointModal from './endpoint-modal'
|
import EndpointModal from './endpoint-modal'
|
||||||
|
import { NAME_FIELD } from './utils'
|
||||||
import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
import CopyBtn from '@/app/components/base/copy-btn'
|
import CopyBtn from '@/app/components/base/copy-btn'
|
||||||
import Confirm from '@/app/components/base/confirm'
|
import Confirm from '@/app/components/base/confirm'
|
||||||
import Indicator from '@/app/components/header/indicator'
|
import Indicator from '@/app/components/header/indicator'
|
||||||
import Switch from '@/app/components/base/switch'
|
import Switch from '@/app/components/base/switch'
|
||||||
|
import Toast from '@/app/components/base/toast'
|
||||||
import {
|
import {
|
||||||
deleteEndpoint,
|
useDeleteEndpoint,
|
||||||
disableEndpoint,
|
useDisableEndpoint,
|
||||||
enableEndpoint,
|
useEnableEndpoint,
|
||||||
updateEndpoint,
|
useUpdateEndpoint,
|
||||||
} from '@/service/plugins'
|
} from '@/service/use-endpoints'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: EndpointListItem
|
data: EndpointListItem
|
||||||
|
handleChange: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const EndpointCard = ({
|
const EndpointCard = ({
|
||||||
data,
|
data,
|
||||||
|
handleChange,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [active, setActive] = useState(data.enabled)
|
const [active, setActive] = useState(data.enabled)
|
||||||
const endpointID = data.id
|
const endpointID = data.id
|
||||||
|
|
||||||
|
// switch
|
||||||
const [isShowDisableConfirm, {
|
const [isShowDisableConfirm, {
|
||||||
setTrue: showDisableConfirm,
|
setTrue: showDisableConfirm,
|
||||||
setFalse: hideDisableConfirm,
|
setFalse: hideDisableConfirm,
|
||||||
}] = useBoolean(false)
|
}] = useBoolean(false)
|
||||||
const activeEndpoint = async () => {
|
const { mutate: enableEndpoint } = useEnableEndpoint({
|
||||||
try {
|
onSuccess: async () => {
|
||||||
await enableEndpoint({
|
await handleChange()
|
||||||
url: '/workspaces/current/endpoints/enable',
|
},
|
||||||
endpointID,
|
onError: () => {
|
||||||
})
|
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
setActive(false)
|
setActive(false)
|
||||||
}
|
},
|
||||||
}
|
})
|
||||||
const inactiveEndpoint = async () => {
|
const { mutate: disableEndpoint } = useDisableEndpoint({
|
||||||
try {
|
onSuccess: async () => {
|
||||||
await disableEndpoint({
|
await handleChange()
|
||||||
url: '/workspaces/current/endpoints/disable',
|
hideDisableConfirm()
|
||||||
endpointID,
|
},
|
||||||
})
|
onError: () => {
|
||||||
}
|
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||||
catch (error) {
|
setActive(false)
|
||||||
console.error(error)
|
},
|
||||||
setActive(true)
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
const handleSwitch = (state: boolean) => {
|
const handleSwitch = (state: boolean) => {
|
||||||
if (state) {
|
if (state) {
|
||||||
setActive(true)
|
setActive(true)
|
||||||
activeEndpoint()
|
enableEndpoint(endpointID)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setActive(false)
|
setActive(false)
|
||||||
@ -67,49 +67,49 @@ const EndpointCard = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delete
|
||||||
const [isShowDeleteConfirm, {
|
const [isShowDeleteConfirm, {
|
||||||
setTrue: showDeleteConfirm,
|
setTrue: showDeleteConfirm,
|
||||||
setFalse: hideDeleteConfirm,
|
setFalse: hideDeleteConfirm,
|
||||||
}] = useBoolean(false)
|
}] = useBoolean(false)
|
||||||
const handleDelete = async () => {
|
const { mutate: deleteEndpoint } = useDeleteEndpoint({
|
||||||
try {
|
onSuccess: async () => {
|
||||||
await deleteEndpoint({
|
await handleChange()
|
||||||
url: '/workspaces/current/endpoints/delete',
|
hideDeleteConfirm()
|
||||||
endpointID,
|
},
|
||||||
})
|
onError: () => {
|
||||||
}
|
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||||
catch (error) {
|
},
|
||||||
console.error(error)
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// update
|
||||||
const [isShowEndpointModal, {
|
const [isShowEndpointModal, {
|
||||||
setTrue: showEndpointModalConfirm,
|
setTrue: showEndpointModalConfirm,
|
||||||
setFalse: hideEndpointModalConfirm,
|
setFalse: hideEndpointModalConfirm,
|
||||||
}] = useBoolean(false)
|
}] = useBoolean(false)
|
||||||
|
|
||||||
const formSchemas = useMemo(() => {
|
const formSchemas = useMemo(() => {
|
||||||
return toolCredentialToFormSchemas(data.declaration.settings)
|
return toolCredentialToFormSchemas([NAME_FIELD, ...data.declaration.settings])
|
||||||
}, [data.declaration.settings])
|
}, [data.declaration.settings])
|
||||||
const formValue = useMemo(() => {
|
const formValue = useMemo(() => {
|
||||||
return addDefaultValue(data.settings, formSchemas)
|
const formValue = {
|
||||||
}, [data.settings, formSchemas])
|
name: data.name,
|
||||||
|
...data.settings,
|
||||||
const handleUpdate = (state: any) => {
|
|
||||||
try {
|
|
||||||
updateEndpoint({
|
|
||||||
url: '/workspaces/current/endpoints',
|
|
||||||
body: {
|
|
||||||
endpoint_id: data.id,
|
|
||||||
settings: state,
|
|
||||||
name: state.name,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
return addDefaultValue(formValue, formSchemas)
|
||||||
console.error(error)
|
}, [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 (
|
return (
|
||||||
<div className='p-0.5 bg-background-section-burn rounded-xl'>
|
<div className='p-0.5 bg-background-section-burn rounded-xl'>
|
||||||
@ -171,7 +171,7 @@ const EndpointCard = ({
|
|||||||
hideDisableConfirm()
|
hideDisableConfirm()
|
||||||
setActive(true)
|
setActive(true)
|
||||||
}}
|
}}
|
||||||
onConfirm={inactiveEndpoint}
|
onConfirm={() => disableEndpoint(endpointID)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isShowDeleteConfirm && (
|
{isShowDeleteConfirm && (
|
||||||
@ -180,7 +180,7 @@ const EndpointCard = ({
|
|||||||
title={t('plugin.detailPanel.endpointDeleteTip')}
|
title={t('plugin.detailPanel.endpointDeleteTip')}
|
||||||
content={<div>{t('plugin.detailPanel.endpointDeleteContent', { name: data.name })}</div>}
|
content={<div>{t('plugin.detailPanel.endpointDeleteContent', { name: data.name })}</div>}
|
||||||
onCancel={hideDeleteConfirm}
|
onCancel={hideDeleteConfirm}
|
||||||
onConfirm={handleDelete}
|
onConfirm={() => deleteEndpoint(endpointID)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isShowEndpointModal && (
|
{isShowEndpointModal && (
|
||||||
|
|||||||
@ -1,65 +1,62 @@
|
|||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import useSWR from 'swr'
|
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import { RiAddLine } from '@remixicon/react'
|
import { RiAddLine } from '@remixicon/react'
|
||||||
import EndpointModal from './endpoint-modal'
|
import EndpointModal from './endpoint-modal'
|
||||||
import EndpointCard from './endpoint-card'
|
import EndpointCard from './endpoint-card'
|
||||||
|
import { NAME_FIELD } from './utils'
|
||||||
import { toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
import { toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
import Toast from '@/app/components/base/toast'
|
||||||
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
|
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
|
||||||
import {
|
import {
|
||||||
createEndpoint,
|
useCreateEndpoint,
|
||||||
fetchEndpointList,
|
useEndpointList,
|
||||||
} from '@/service/plugins'
|
useInvalidateEndpointList,
|
||||||
|
} from '@/service/use-endpoints'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
const EndpointList = () => {
|
type Props = {
|
||||||
|
showTopBorder?: boolean
|
||||||
|
}
|
||||||
|
const EndpointList = ({ showTopBorder }: Props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const pluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
const pluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
||||||
const pluginUniqueID = pluginDetail.plugin_unique_identifier
|
const pluginUniqueID = pluginDetail.plugin_unique_identifier
|
||||||
const declaration = pluginDetail.declaration.endpoint
|
const declaration = pluginDetail.declaration.endpoint
|
||||||
const { data } = useSWR(
|
const { data } = useEndpointList(pluginDetail.plugin_id)
|
||||||
{
|
const invalidateEndpointList = useInvalidateEndpointList()
|
||||||
url: '/workspaces/current/endpoints/list/plugin',
|
|
||||||
params: {
|
|
||||||
plugin_id: pluginDetail.plugin_id,
|
|
||||||
page: 1,
|
|
||||||
page_size: 100,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fetchEndpointList,
|
|
||||||
)
|
|
||||||
const [isShowEndpointModal, {
|
const [isShowEndpointModal, {
|
||||||
setTrue: showEndpointModal,
|
setTrue: showEndpointModal,
|
||||||
setFalse: hideEndpointModal,
|
setFalse: hideEndpointModal,
|
||||||
}] = useBoolean(false)
|
}] = useBoolean(false)
|
||||||
|
|
||||||
const formSchemas = useMemo(() => {
|
const formSchemas = useMemo(() => {
|
||||||
return toolCredentialToFormSchemas(declaration.settings)
|
return toolCredentialToFormSchemas([NAME_FIELD, ...declaration.settings])
|
||||||
}, [declaration.settings])
|
}, [declaration.settings])
|
||||||
|
|
||||||
const handleCreate = (state: any) => {
|
const { mutate: createEndpoint } = useCreateEndpoint({
|
||||||
try {
|
onSuccess: async () => {
|
||||||
createEndpoint({
|
await invalidateEndpointList(pluginDetail.plugin_id)
|
||||||
url: '/workspaces/current/endpoints',
|
hideEndpointModal()
|
||||||
body: {
|
},
|
||||||
plugin_unique_identifier: pluginUniqueID,
|
onError: () => {
|
||||||
settings: state,
|
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||||
name: state.name,
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
}
|
const handleCreate = (state: any) => createEndpoint({
|
||||||
catch (error) {
|
pluginUniqueID,
|
||||||
console.error(error)
|
state,
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
if (!data)
|
if (!data)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
return (
|
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='mb-1 h-6 flex items-center justify-between text-text-secondary system-sm-semibold-uppercase'>
|
||||||
<div className='flex items-center gap-0.5'>
|
<div className='flex items-center gap-0.5'>
|
||||||
{t('plugin.detailPanel.endpoints')}
|
{t('plugin.detailPanel.endpoints')}
|
||||||
@ -81,6 +78,7 @@ const EndpointList = () => {
|
|||||||
<EndpointCard
|
<EndpointCard
|
||||||
key={index}
|
key={index}
|
||||||
data={item}
|
data={item}
|
||||||
|
handleChange={() => invalidateEndpointList(pluginDetail.plugin_id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,11 +10,11 @@ import { usePluginPageContext } from '@/app/components/plugins/plugin-page/conte
|
|||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onDelete: () => void
|
onUpdate: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const PluginDetailPanel: FC<Props> = ({
|
const PluginDetailPanel: FC<Props> = ({
|
||||||
onDelete,
|
onUpdate,
|
||||||
}) => {
|
}) => {
|
||||||
const pluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
const pluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
||||||
const setCurrentPluginDetail = usePluginPageContext(v => v.setCurrentPluginDetail)
|
const setCurrentPluginDetail = usePluginPageContext(v => v.setCurrentPluginDetail)
|
||||||
@ -39,11 +39,11 @@ const PluginDetailPanel: FC<Props> = ({
|
|||||||
<DetailHeader
|
<DetailHeader
|
||||||
detail={pluginDetail}
|
detail={pluginDetail}
|
||||||
onHide={handleHide}
|
onHide={handleHide}
|
||||||
onDelete={onDelete}
|
onUpdate={onUpdate}
|
||||||
/>
|
/>
|
||||||
<div className='grow overflow-y-auto'>
|
<div className='grow overflow-y-auto'>
|
||||||
{!!pluginDetail.declaration.endpoint && <EndpointList />}
|
|
||||||
{!!pluginDetail.declaration.tool && <ActionList />}
|
{!!pluginDetail.declaration.tool && <ActionList />}
|
||||||
|
{!!pluginDetail.declaration.endpoint && <EndpointList showTopBorder={!!pluginDetail.declaration.tool} />}
|
||||||
{!!pluginDetail.declaration.model && <ModelList />}
|
{!!pluginDetail.declaration.model && <ModelList />}
|
||||||
</div>
|
</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 React from 'react'
|
||||||
import useSWR from 'swr'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
|
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
|
||||||
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
|
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 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 ModelList = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
||||||
|
const { data: res } = useModelProviderModelList(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
|
||||||
const { data: res } = useSWR(
|
|
||||||
`/workspaces/current/model-providers/${currentPluginDetail.plugin_id}/${currentPluginDetail.name}/models`,
|
|
||||||
fetchModelProviderModelList,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!res)
|
if (!res)
|
||||||
return null
|
return null
|
||||||
@ -21,7 +16,7 @@ const ModelList = () => {
|
|||||||
return (
|
return (
|
||||||
<div className='px-4 py-2'>
|
<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='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 => (
|
{res.data.map(model => (
|
||||||
<div key={model.model} className='h-6 py-1 flex items-center'>
|
<div key={model.model} className='h-6 py-1 flex items-center'>
|
||||||
<ModelIcon
|
<ModelIcon
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback, useRef, useState } from 'react'
|
import React, { useCallback, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { PluginSource } from '../types'
|
||||||
import { RiArrowRightUpLine, RiMoreFill } from '@remixicon/react'
|
import { RiArrowRightUpLine, RiMoreFill } from '@remixicon/react'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
// import Button from '@/app/components/base/button'
|
// import Button from '@/app/components/base/button'
|
||||||
@ -13,6 +14,7 @@ import {
|
|||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
source: PluginSource
|
||||||
onInfo: () => void
|
onInfo: () => void
|
||||||
onCheckVersion: () => void
|
onCheckVersion: () => void
|
||||||
onRemove: () => void
|
onRemove: () => void
|
||||||
@ -20,10 +22,11 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const OperationDropdown: FC<Props> = ({
|
const OperationDropdown: FC<Props> = ({
|
||||||
|
source,
|
||||||
|
detailUrl,
|
||||||
onInfo,
|
onInfo,
|
||||||
onCheckVersion,
|
onCheckVersion,
|
||||||
onRemove,
|
onRemove,
|
||||||
detailUrl,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [open, doSetOpen] = useState(false)
|
const [open, doSetOpen] = useState(false)
|
||||||
@ -56,25 +59,33 @@ const OperationDropdown: FC<Props> = ({
|
|||||||
</PortalToFollowElemTrigger>
|
</PortalToFollowElemTrigger>
|
||||||
<PortalToFollowElemContent className='z-50'>
|
<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 className='w-[160px] p-1 bg-components-panel-bg-blur rounded-xl border-[0.5px] border-components-panel-border shadow-lg'>
|
||||||
<div
|
{source === PluginSource.github && (
|
||||||
onClick={() => {
|
<div
|
||||||
onInfo()
|
onClick={() => {
|
||||||
handleTrigger()
|
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>
|
className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'
|
||||||
<div
|
>{t('plugin.detailPanel.operation.info')}</div>
|
||||||
onClick={() => {
|
)}
|
||||||
onCheckVersion()
|
{source === PluginSource.github && (
|
||||||
handleTrigger()
|
<div
|
||||||
}}
|
onClick={() => {
|
||||||
className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'
|
onCheckVersion()
|
||||||
>{t('plugin.detailPanel.operation.checkUpdate')}</div>
|
handleTrigger()
|
||||||
<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>
|
className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'
|
||||||
<RiArrowRightUpLine className='shrink-0 w-3.5 h-3.5 text-text-tertiary' />
|
>{t('plugin.detailPanel.operation.checkUpdate')}</div>
|
||||||
</a>
|
)}
|
||||||
<div className='my-1 h-px bg-divider-subtle'></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
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onRemove()
|
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,
|
useContextSelector,
|
||||||
} from 'use-context-selector'
|
} from 'use-context-selector'
|
||||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
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 type { FilterState } from './filter-management'
|
||||||
import { PermissionType } from '../types'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
||||||
|
|
||||||
export type PluginPageContextValue = {
|
export type PluginPageContextValue = {
|
||||||
containerRef: React.RefObject<HTMLDivElement>
|
containerRef: React.RefObject<HTMLDivElement>
|
||||||
permissions: Permissions
|
|
||||||
setPermissions: (permissions: PluginPageContextValue['permissions']) => void
|
|
||||||
currentPluginDetail: PluginDetail | undefined
|
currentPluginDetail: PluginDetail | undefined
|
||||||
setCurrentPluginDetail: (plugin: PluginDetail) => void
|
setCurrentPluginDetail: (plugin: PluginDetail) => void
|
||||||
filters: FilterState
|
filters: FilterState
|
||||||
@ -32,21 +29,16 @@ export type PluginPageContextValue = {
|
|||||||
|
|
||||||
export const PluginPageContext = createContext<PluginPageContextValue>({
|
export const PluginPageContext = createContext<PluginPageContextValue>({
|
||||||
containerRef: { current: null },
|
containerRef: { current: null },
|
||||||
permissions: {
|
|
||||||
install_permission: PermissionType.noOne,
|
|
||||||
debug_permission: PermissionType.noOne,
|
|
||||||
},
|
|
||||||
setPermissions: () => {},
|
|
||||||
currentPluginDetail: undefined,
|
currentPluginDetail: undefined,
|
||||||
setCurrentPluginDetail: () => {},
|
setCurrentPluginDetail: () => { },
|
||||||
filters: {
|
filters: {
|
||||||
categories: [],
|
categories: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
},
|
},
|
||||||
setFilters: () => {},
|
setFilters: () => { },
|
||||||
activeTab: '',
|
activeTab: '',
|
||||||
setActiveTab: () => {},
|
setActiveTab: () => { },
|
||||||
options: [],
|
options: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -63,10 +55,6 @@ export const PluginPageContextProvider = ({
|
|||||||
}: PluginPageContextProviderProps) => {
|
}: PluginPageContextProviderProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
const [permissions, setPermissions] = useState<PluginPageContextValue['permissions']>({
|
|
||||||
install_permission: PermissionType.noOne,
|
|
||||||
debug_permission: PermissionType.noOne,
|
|
||||||
})
|
|
||||||
const [filters, setFilters] = useState<FilterState>({
|
const [filters, setFilters] = useState<FilterState>({
|
||||||
categories: [],
|
categories: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
@ -93,8 +81,6 @@ export const PluginPageContextProvider = ({
|
|||||||
<PluginPageContext.Provider
|
<PluginPageContext.Provider
|
||||||
value={{
|
value={{
|
||||||
containerRef,
|
containerRef,
|
||||||
permissions,
|
|
||||||
setPermissions,
|
|
||||||
currentPluginDetail,
|
currentPluginDetail,
|
||||||
setCurrentPluginDetail,
|
setCurrentPluginDetail,
|
||||||
filters,
|
filters,
|
||||||
|
|||||||
@ -206,7 +206,7 @@ const PluginPage = ({
|
|||||||
|
|
||||||
{showPluginSettingModal && (
|
{showPluginSettingModal && (
|
||||||
<PermissionSetModal
|
<PermissionSetModal
|
||||||
payload={permissions}
|
payload={permissions!}
|
||||||
onHide={setHidePluginSettingModal}
|
onHide={setHidePluginSettingModal}
|
||||||
onSave={setPermissions}
|
onSave={setPermissions}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -48,7 +48,7 @@ const PluginsPanel = () => {
|
|||||||
) : (
|
) : (
|
||||||
<Empty />
|
<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 { PermissionType } from '../types'
|
||||||
import {
|
|
||||||
usePluginPageContext,
|
|
||||||
} from './context'
|
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import { updatePermission as doUpdatePermission, fetchPermission } from '@/service/plugins'
|
|
||||||
import Toast from '../../base/toast'
|
import Toast from '../../base/toast'
|
||||||
import { useTranslation } from 'react-i18next'
|
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)
|
if (permission === PermissionType.noOne)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@ -22,29 +19,26 @@ const hasPermission = (permission: PermissionType, isAdmin: boolean) => {
|
|||||||
const usePermission = () => {
|
const usePermission = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { isCurrentWorkspaceManager, isCurrentWorkspaceOwner } = useAppContext()
|
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 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 {
|
return {
|
||||||
canManagement: hasPermission(permissions.install_permission, isAdmin),
|
canManagement: hasPermission(permissions?.install_permission, isAdmin),
|
||||||
canDebugger: hasPermission(permissions.debug_permission, isAdmin),
|
canDebugger: hasPermission(permissions?.debug_permission, isAdmin),
|
||||||
canSetPermissions: isAdmin,
|
canSetPermissions: isAdmin,
|
||||||
permissions,
|
permissions,
|
||||||
setPermissions: updatePermission,
|
setPermissions: updatePermission,
|
||||||
|
isUpdatePending,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,11 +19,13 @@ import { useBoolean } from 'ahooks'
|
|||||||
type Props = {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
payload: Plugin
|
payload: Plugin
|
||||||
|
onSuccess: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProviderCard: FC<Props> = ({
|
const ProviderCard: FC<Props> = ({
|
||||||
className,
|
className,
|
||||||
payload,
|
payload,
|
||||||
|
onSuccess,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [isShowInstallFromMarketplace, {
|
const [isShowInstallFromMarketplace, {
|
||||||
@ -84,7 +86,10 @@ const ProviderCard: FC<Props> = ({
|
|||||||
manifest={payload as any}
|
manifest={payload as any}
|
||||||
uniqueIdentifier={payload.latest_package_identifier}
|
uniqueIdentifier={payload.latest_package_identifier}
|
||||||
onClose={hideInstallFromMarketplace}
|
onClose={hideInstallFromMarketplace}
|
||||||
onSuccess={hideInstallFromMarketplace}
|
onSuccess={() => {
|
||||||
|
onSuccess()
|
||||||
|
hideInstallFromMarketplace()
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -194,19 +194,10 @@ export type GitHubUrlInfo = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// endpoint
|
// endpoint
|
||||||
export type CreateEndpointRequest = {
|
|
||||||
plugin_unique_identifier: string
|
|
||||||
settings: Record<string, any>
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
export type EndpointOperationResponse = {
|
export type EndpointOperationResponse = {
|
||||||
result: 'success' | 'error'
|
result: 'success' | 'error'
|
||||||
}
|
}
|
||||||
export type EndpointsRequest = {
|
|
||||||
page_size: number
|
|
||||||
page: number
|
|
||||||
plugin_id: string
|
|
||||||
}
|
|
||||||
export type EndpointsResponse = {
|
export type EndpointsResponse = {
|
||||||
endpoints: EndpointListItem[]
|
endpoints: EndpointListItem[]
|
||||||
has_more: boolean
|
has_more: boolean
|
||||||
@ -301,3 +292,7 @@ export type InstalledPluginListResponse = {
|
|||||||
export type UninstallPluginResponse = {
|
export type UninstallPluginResponse = {
|
||||||
success: boolean
|
success: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PluginsFromMarketplaceResponse = {
|
||||||
|
plugins: Plugin[]
|
||||||
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
|
|||||||
} = useMarketplaceCollectionsAndPlugins()
|
} = useMarketplaceCollectionsAndPlugins()
|
||||||
const {
|
const {
|
||||||
plugins,
|
plugins,
|
||||||
setPlugins,
|
resetPlugins,
|
||||||
queryPlugins,
|
queryPlugins,
|
||||||
queryPluginsWithDebounced,
|
queryPluginsWithDebounced,
|
||||||
isLoading: isPluginsLoading,
|
isLoading: isPluginsLoading,
|
||||||
@ -37,9 +37,9 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
queryMarketplaceCollectionsAndPlugins()
|
queryMarketplaceCollectionsAndPlugins()
|
||||||
setPlugins(undefined)
|
resetPlugins()
|
||||||
}
|
}
|
||||||
}, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, setPlugins])
|
}, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, resetPlugins])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoading: isLoading || isPluginsLoading,
|
isLoading: isLoading || isPluginsLoading,
|
||||||
|
|||||||
@ -31,9 +31,11 @@ const Marketplace = ({
|
|||||||
onClick={() => onMarketplaceScroll()}
|
onClick={() => onMarketplaceScroll()}
|
||||||
/>
|
/>
|
||||||
<div className='sticky top-0 pt-5 pb-3 bg-background-default-subtle z-10'>
|
<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'>
|
<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">
|
<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')}
|
{t('plugin.category.models')}
|
||||||
</span>
|
</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">
|
<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')}
|
{t('plugin.category.extensions')}
|
||||||
</span>
|
</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">
|
<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')}
|
{t('plugin.category.bundles')}
|
||||||
</span>
|
</span>
|
||||||
in Dify Marketplace
|
{t('plugin.marketplace.inDifyMarketplace')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export type Tool = {
|
|||||||
export type ToolCredential = {
|
export type ToolCredential = {
|
||||||
name: string
|
name: string
|
||||||
label: TypeWithI18N
|
label: TypeWithI18N
|
||||||
help: TypeWithI18N
|
help: TypeWithI18N | null
|
||||||
placeholder: TypeWithI18N
|
placeholder: TypeWithI18N
|
||||||
type: string
|
type: string
|
||||||
required: boolean
|
required: boolean
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
const translation = {
|
|
||||||
plugins: {
|
|
||||||
title: 'Plugins',
|
|
||||||
},
|
|
||||||
discover: {
|
|
||||||
title: 'Explore Marketplace',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default translation
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
const translation = {
|
const translation = {
|
||||||
category: {
|
category: {
|
||||||
|
all: 'All',
|
||||||
models: 'models',
|
models: 'models',
|
||||||
tools: 'tools',
|
tools: 'tools',
|
||||||
extensions: 'extensions',
|
extensions: 'extensions',
|
||||||
@ -116,6 +117,13 @@ const translation = {
|
|||||||
error: {
|
error: {
|
||||||
inValidGitHubUrl: 'Invalid GitHub URL. Please enter a valid URL in the format: https://github.com/owner/repo',
|
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
|
export default translation
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
const translation = {
|
|
||||||
plugins: {
|
|
||||||
title: '插件',
|
|
||||||
},
|
|
||||||
discover: {
|
|
||||||
title: '探索市场',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default translation
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
const translation = {
|
const translation = {
|
||||||
category: {
|
category: {
|
||||||
|
all: '全部',
|
||||||
models: '模型',
|
models: '模型',
|
||||||
tools: '工具',
|
tools: '工具',
|
||||||
extensions: '扩展',
|
extensions: '扩展',
|
||||||
@ -116,6 +117,13 @@ const translation = {
|
|||||||
error: {
|
error: {
|
||||||
inValidGitHubUrl: '无效的 GitHub URL。请输入格式为 https://github.com/owner/repo 的有效 URL',
|
inValidGitHubUrl: '无效的 GitHub URL。请输入格式为 https://github.com/owner/repo 的有效 URL',
|
||||||
},
|
},
|
||||||
|
marketplace: {
|
||||||
|
empower: '助力您的 AI 开发',
|
||||||
|
discover: '探索',
|
||||||
|
and: '和',
|
||||||
|
inDifyMarketplace: '在 Dify 市场中',
|
||||||
|
moreFrom: '更多来自市场',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translation
|
export default translation
|
||||||
|
|||||||
@ -64,6 +64,7 @@
|
|||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"katex": "^0.16.11",
|
"katex": "^0.16.11",
|
||||||
|
"ky": "^1.7.2",
|
||||||
"lamejs": "^1.2.1",
|
"lamejs": "^1.2.1",
|
||||||
"lexical": "^0.18.0",
|
"lexical": "^0.18.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
@ -84,9 +85,9 @@
|
|||||||
"react-hook-form": "^7.53.1",
|
"react-hook-form": "^7.53.1",
|
||||||
"react-i18next": "^15.1.0",
|
"react-i18next": "^15.1.0",
|
||||||
"react-infinite-scroll-component": "^6.1.0",
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
|
"react-markdown": "^9.0.1",
|
||||||
"react-multi-email": "^1.0.25",
|
"react-multi-email": "^1.0.25",
|
||||||
"react-papaparse": "^4.4.0",
|
"react-papaparse": "^4.4.0",
|
||||||
"react-markdown": "^9.0.1",
|
|
||||||
"react-slider": "^2.0.6",
|
"react-slider": "^2.0.6",
|
||||||
"react-sortablejs": "^6.1.4",
|
"react-sortablejs": "^6.1.4",
|
||||||
"react-syntax-highlighter": "^15.6.1",
|
"react-syntax-highlighter": "^15.6.1",
|
||||||
|
|||||||
9
web/pnpm-lock.yaml
generated
9
web/pnpm-lock.yaml
generated
@ -133,6 +133,9 @@ importers:
|
|||||||
katex:
|
katex:
|
||||||
specifier: ^0.16.11
|
specifier: ^0.16.11
|
||||||
version: 0.16.11
|
version: 0.16.11
|
||||||
|
ky:
|
||||||
|
specifier: ^1.7.2
|
||||||
|
version: 1.7.2
|
||||||
lamejs:
|
lamejs:
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
@ -5612,6 +5615,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
ky@1.7.2:
|
||||||
|
resolution: {integrity: sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
lamejs@1.2.1:
|
lamejs@1.2.1:
|
||||||
resolution: {integrity: sha512-s7bxvjvYthw6oPLCm5pFxvA84wUROODB8jEO2+CE1adhKgrIvVOlmMgY8zyugxGrvRaDHNJanOiS21/emty6dQ==}
|
resolution: {integrity: sha512-s7bxvjvYthw6oPLCm5pFxvA84wUROODB8jEO2+CE1adhKgrIvVOlmMgY8zyugxGrvRaDHNJanOiS21/emty6dQ==}
|
||||||
|
|
||||||
@ -14744,6 +14751,8 @@ snapshots:
|
|||||||
|
|
||||||
kleur@4.1.5: {}
|
kleur@4.1.5: {}
|
||||||
|
|
||||||
|
ky@1.7.2: {}
|
||||||
|
|
||||||
lamejs@1.2.1:
|
lamejs@1.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
use-strict: 1.0.1
|
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 { refreshAccessTokenOrRelogin } from './refresh-token'
|
||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type'
|
import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type'
|
||||||
@ -17,27 +17,10 @@ import type {
|
|||||||
WorkflowStartedResponse,
|
WorkflowStartedResponse,
|
||||||
} from '@/types/workflow'
|
} from '@/types/workflow'
|
||||||
import { removeAccessToken } from '@/app/components/share/utils'
|
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 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 = {
|
export type IOnDataMoreInfo = {
|
||||||
conversationId?: string
|
conversationId?: string
|
||||||
taskId?: string
|
taskId?: string
|
||||||
@ -100,17 +83,6 @@ export type IOtherOptions = {
|
|||||||
onTextReplace?: IOnTextReplace
|
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) {
|
function unicodeToChar(text: string) {
|
||||||
if (!text)
|
if (!text)
|
||||||
return ''
|
return ''
|
||||||
@ -277,153 +249,13 @@ const handleStream = (
|
|||||||
read()
|
read()
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseFetch = <T>(
|
const baseFetch = base
|
||||||
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>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const upload = (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise<any> => {
|
export const upload = (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise<any> => {
|
||||||
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
|
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
|
||||||
let token = ''
|
let token = ''
|
||||||
if (isPublicAPI) {
|
if (isPublicAPI) {
|
||||||
const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0]
|
token = getPublicToken()
|
||||||
const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' })
|
|
||||||
let accessTokenJson = { [sharedToken]: '' }
|
|
||||||
try {
|
|
||||||
accessTokenJson = JSON.parse(accessToken)
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
token = accessTokenJson[sharedToken]
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const accessToken = localStorage.getItem('console_token') || ''
|
const accessToken = localStorage.getItem('console_token') || ''
|
||||||
@ -499,9 +331,9 @@ export const ssePost = (
|
|||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
}, fetchOptions)
|
}, fetchOptions)
|
||||||
|
|
||||||
const contentType = options.headers.get('Content-Type')
|
const contentType = (options.headers as Headers).get('Content-Type')
|
||||||
if (!contentType)
|
if (!contentType)
|
||||||
options.headers.set('Content-Type', ContentType.json)
|
(options.headers as Headers).set('Content-Type', ContentType.json)
|
||||||
|
|
||||||
getAbortController?.(abortController)
|
getAbortController?.(abortController)
|
||||||
|
|
||||||
@ -559,18 +391,17 @@ export const ssePost = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// base request
|
// 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) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
const otherOptionsForBaseFetch = otherOptions || {}
|
baseFetch<T>(url, options, otherOptions).then(resolve).catch((errResp) => {
|
||||||
baseFetch<T>(url, options, otherOptionsForBaseFetch).then(resolve).catch((errResp) => {
|
|
||||||
if (errResp?.status === 401) {
|
if (errResp?.status === 401) {
|
||||||
return refreshAccessTokenOrRelogin(TIME_OUT).then(() => {
|
return refreshAccessTokenOrRelogin(TIME_OUT).then(() => {
|
||||||
baseFetch<T>(url, options, otherOptionsForBaseFetch).then(resolve).catch(reject)
|
baseFetch<T>(url, options, otherOptions).then(resolve).catch(reject)
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
const {
|
const {
|
||||||
isPublicAPI = false,
|
isPublicAPI = false,
|
||||||
silent,
|
silent,
|
||||||
} = otherOptionsForBaseFetch
|
} = otherOptions
|
||||||
const bodyJson = errResp.json()
|
const bodyJson = errResp.json()
|
||||||
if (isPublicAPI) {
|
if (isPublicAPI) {
|
||||||
return bodyJson.then((data: ResponseError) => {
|
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)
|
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) => {
|
export const postPublic = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||||
return post<T>(url, options, { ...otherOptions, isPublicAPI: true })
|
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 type { Fetcher } from 'swr'
|
||||||
import { del, get, getMarketplace, post, upload } from './base'
|
import { get, getMarketplace, post, upload } from './base'
|
||||||
import type {
|
import type {
|
||||||
CreateEndpointRequest,
|
|
||||||
EndpointOperationResponse,
|
|
||||||
EndpointsRequest,
|
|
||||||
EndpointsResponse,
|
|
||||||
InstallPackageResponse,
|
InstallPackageResponse,
|
||||||
Permissions,
|
Permissions,
|
||||||
PluginDeclaration,
|
PluginDeclaration,
|
||||||
@ -12,49 +8,13 @@ import type {
|
|||||||
PluginTasksResponse,
|
PluginTasksResponse,
|
||||||
TaskStatusResponse,
|
TaskStatusResponse,
|
||||||
UninstallPluginResponse,
|
UninstallPluginResponse,
|
||||||
UpdateEndpointRequest,
|
|
||||||
uploadGitHubResponse,
|
uploadGitHubResponse,
|
||||||
} from '@/app/components/plugins/types'
|
} from '@/app/components/plugins/types'
|
||||||
import type { DebugInfo as DebugInfoTypes } from '@/app/components/plugins/types'
|
|
||||||
import type {
|
import type {
|
||||||
MarketplaceCollectionPluginsResponse,
|
MarketplaceCollectionPluginsResponse,
|
||||||
MarketplaceCollectionsResponse,
|
MarketplaceCollectionsResponse,
|
||||||
} from '@/app/components/plugins/marketplace/types'
|
} 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) => {
|
export const uploadPackageFile = async (file: File) => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('pkg', file)
|
formData.append('pkg', file)
|
||||||
@ -64,12 +24,6 @@ export const uploadPackageFile = async (file: File) => {
|
|||||||
}, false, '/workspaces/current/plugin/upload/pkg')
|
}, 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>) => {
|
export const updateFromMarketPlace = async (body: Record<string, string>) => {
|
||||||
return post<InstallPackageResponse>('/workspaces/current/plugin/upgrade/marketplace', {
|
return post<InstallPackageResponse>('/workspaces/current/plugin/upgrade/marketplace', {
|
||||||
body,
|
body,
|
||||||
@ -109,12 +63,6 @@ export const fetchManifestFromMarketPlace = async (uniqueIdentifier: string) =>
|
|||||||
return getMarketplace<{ data: { plugin: PluginManifestInMarket } }>(`/plugins/identifier?unique_identifier=${uniqueIdentifier}`)
|
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 }) => {
|
export const fetchMarketplaceCollections: Fetcher<MarketplaceCollectionsResponse, { url: string; }> = ({ url }) => {
|
||||||
return get<MarketplaceCollectionsResponse>(url)
|
return get<MarketplaceCollectionsResponse>(url)
|
||||||
}
|
}
|
||||||
@ -131,10 +79,6 @@ export const checkTaskStatus = async (taskId: string) => {
|
|||||||
return get<TaskStatusResponse>(`/workspaces/current/plugin/tasks/${taskId}`)
|
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) => {
|
export const updatePermission = async (permissions: Permissions) => {
|
||||||
return post('/workspaces/current/plugin/permission/change', { body: 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 type {
|
||||||
import { get } from './base'
|
DebugInfo as DebugInfoTypes,
|
||||||
import {
|
InstallPackageResponse,
|
||||||
useQueryClient,
|
InstalledPluginListResponse,
|
||||||
} from '@tanstack/react-query'
|
Permissions,
|
||||||
|
PluginsFromMarketplaceResponse,
|
||||||
|
} from '@/app/components/plugins/types'
|
||||||
|
import type {
|
||||||
|
PluginsSearchParams,
|
||||||
|
} from '@/app/components/plugins/marketplace/types'
|
||||||
|
import { get, post, postMarketplace } from './base'
|
||||||
import {
|
import {
|
||||||
|
useMutation,
|
||||||
useQuery,
|
useQuery,
|
||||||
|
useQueryClient,
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
const NAME_SPACE = 'plugins'
|
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 = () => {
|
export const useDebugKey = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [NAME_SPACE, 'debugKey'],
|
queryKey: [NAME_SPACE, 'debugKey'],
|
||||||
queryFn: () => get<DebugInfoTypes>('/workspaces/current/plugin/debugging-key'),
|
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 {
|
import type {
|
||||||
|
Collection,
|
||||||
Tool,
|
Tool,
|
||||||
} from '@/app/components/tools/types'
|
} from '@/app/components/tools/types'
|
||||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||||
import {
|
import {
|
||||||
useQueryClient,
|
useMutation,
|
||||||
} from '@tanstack/react-query'
|
|
||||||
|
|
||||||
import {
|
|
||||||
useQuery,
|
useQuery,
|
||||||
|
useQueryClient,
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
const NAME_SPACE = 'tools'
|
const NAME_SPACE = 'tools'
|
||||||
@ -45,9 +44,61 @@ export const useAllWorkflowTools = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useBuiltInTools = (collectionName: string) => {
|
export const useBuiltinProviderInfo = (providerName: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [NAME_SPACE, 'builtIn', collectionName],
|
queryKey: [NAME_SPACE, 'builtin-provider-info', providerName],
|
||||||
queryFn: () => get<Tool[]>(`/workspaces/current/tool-provider/builtin/${collectionName}/tools`),
|
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