Merge branch 'feat/plugins' into dev/plugin-deploy

This commit is contained in:
JzoNg 2024-11-28 17:30:56 +08:00
commit adba60d067
13 changed files with 227 additions and 58 deletions

View File

@ -44,13 +44,17 @@ const AgentTools: FC = () => {
const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null)
const currentCollection = useMemo(() => {
if (!currentTool) return null
const collection = collectionList.find(collection => collection.id === currentTool?.provider_id.split('/').pop() && collection.type === currentTool?.provider_type)
const collection = collectionList.find(collection => collection.id.split('/').pop() === currentTool?.provider_id.split('/').pop() && collection.type === currentTool?.provider_type)
return collection
}, [currentTool, collectionList])
const [isShowSettingTool, setIsShowSettingTool] = useState(false)
const [isShowSettingAuth, setShowSettingAuth] = useState(false)
const tools = (modelConfig?.agentConfig?.tools as AgentTool[] || []).map((item) => {
const collection = collectionList.find(collection => collection.id === item.provider_id.split('/').pop() && collection.type === item.provider_type)
const collection = collectionList.find(
collection =>
collection.id.split('/').pop() === item.provider_id.split('/').pop()
&& collection.type === item.provider_type,
)
const icon = collection?.icon
return {
...item,

View File

@ -622,7 +622,7 @@ const Configuration: FC = () => {
}).map((tool: any) => {
return {
...tool,
isDeleted: res.deleted_tools?.includes(tool.tool_name),
isDeleted: res.deleted_tools?.some((deletedTool: any) => deletedTool.id === tool.id && deletedTool.tool_name === tool.tool_name),
notAuthor: collectionList.find(c => tool.provider_id === c.id)?.is_team_authorization === false,
...(tool.provider_type === 'builtin' ? {
provider_id: correctProvider(tool.provider_name),

View File

@ -0,0 +1,39 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import Badge, { BadgeState } from '@/app/components/base/badge/index'
type Props = {
hasInstalled: boolean
installedVersion?: string
toInstallVersion: string
}
const Version: FC<Props> = ({
hasInstalled,
installedVersion,
toInstallVersion,
}) => {
return (
<>
{
!hasInstalled
? (
<Badge className='mx-1' size="s" state={BadgeState.Default}>{toInstallVersion}</Badge>
)
: (
<>
<Badge className='mx-1' size="s" state={BadgeState.Warning}>
{`${installedVersion} -> ${toInstallVersion}`}
</Badge>
{/* <div className='flex px-0.5 justify-center items-center gap-0.5'>
<div className='text-text-warning system-xs-medium'>Used in 3 apps</div>
<RiInformation2Line className='w-4 h-4 text-text-tertiary' />
</div> */}
</>
)
}
</>
)
}
export default React.memo(Version)

View File

@ -0,0 +1,34 @@
import { useCheckInstalled as useDoCheckInstalled } from '@/service/use-plugins'
import { useMemo } from 'react'
type Props = {
pluginIds: string[],
enabled: boolean
}
const useCheckInstalled = (props: Props) => {
const { data, isLoading, error } = useDoCheckInstalled(props)
const installedInfo = useMemo(() => {
if (!data)
return undefined
const res: Record<string, {
installedVersion: string,
uniqueIdentifier: string
}> = {}
data?.plugins.forEach((plugin) => {
res[plugin.plugin_id] = {
installedVersion: plugin.declaration.version,
uniqueIdentifier: plugin.plugin_unique_identifier,
}
})
return res
}, [data])
return {
installedInfo,
isLoading,
error,
}
}
export default useCheckInstalled

View File

@ -1,16 +1,16 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import React, { useEffect } from 'react'
import type { PluginDeclaration } from '../../../types'
import Card from '../../../card'
import { pluginManifestToCardPluginProps } from '../../utils'
import Button from '@/app/components/base/button'
import { Trans, useTranslation } from 'react-i18next'
import { RiLoader2Line } from '@remixicon/react'
import Badge, { BadgeState } from '@/app/components/base/badge/index'
import { useInstallPackageFromLocal } from '@/service/use-plugins'
import checkTaskStatus from '../../base/check-task-status'
import { usePluginTaskList } from '@/service/use-plugins'
import { useInstallPackageFromLocal, usePluginTaskList, useUpdatePackageFromMarketPlace } from '@/service/use-plugins'
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
import Version from '../../base/version'
const i18nPrefix = 'plugin.installModal'
@ -32,8 +32,24 @@ const Installed: FC<Props> = ({
onFailed,
}) => {
const { t } = useTranslation()
const toInstallVersion = payload.version
const pluginId = `${payload.author}/${payload.name}`
const { installedInfo, isLoading } = useCheckInstalled({
pluginIds: [pluginId],
enabled: !!pluginId,
})
const installedInfoPayload = installedInfo?.[pluginId]
const installedVersion = installedInfoPayload?.installedVersion
const hasInstalled = !!installedVersion
useEffect(() => {
if (hasInstalled && toInstallVersion === installedVersion)
onInstalled()
}, [hasInstalled, toInstallVersion, installedVersion])
const [isInstalling, setIsInstalling] = React.useState(false)
const { mutateAsync: installPackageFromLocal } = useInstallPackageFromLocal()
const { mutateAsync: updatePackageFromMarketPlace } = useUpdatePackageFromMarketPlace()
const {
check,
@ -52,10 +68,28 @@ const Installed: FC<Props> = ({
onStartToInstall?.()
try {
const {
all_installed: isInstalled,
task_id: taskId,
} = await installPackageFromLocal(uniqueIdentifier)
let taskId
let isInstalled
if (hasInstalled) {
const {
all_installed,
task_id,
} = await updatePackageFromMarketPlace({
original_plugin_unique_identifier: installedInfoPayload.uniqueIdentifier,
new_plugin_unique_identifier: uniqueIdentifier,
})
taskId = task_id
isInstalled = all_installed
}
else {
const {
all_installed,
task_id,
} = await installPackageFromLocal(uniqueIdentifier)
taskId = task_id
isInstalled = all_installed
}
if (isInstalled) {
onInstalled()
return
@ -92,7 +126,11 @@ const Installed: FC<Props> = ({
<Card
className='w-full'
payload={pluginManifestToCardPluginProps(payload)}
titleLeft={<Badge className='mx-1' size="s" state={BadgeState.Default}>{payload.version}</Badge>}
titleLeft={!isLoading && <Version
hasInstalled={hasInstalled}
installedVersion={installedVersion}
toInstallVersion={toInstallVersion}
/>}
/>
</div>
</div>
@ -106,7 +144,7 @@ const Installed: FC<Props> = ({
<Button
variant='primary'
className='min-w-[72px] flex space-x-0.5'
disabled={isInstalling}
disabled={isInstalling || isLoading}
onClick={handleInstall}
>
{isInstalling && <RiLoader2Line className='w-4 h-4 animate-spin-slow' />}

View File

@ -1,16 +1,17 @@
'use client'
import type { FC } from 'react'
import React, { useMemo } from 'react'
import { RiInformation2Line } from '@remixicon/react'
import React, { useEffect } from 'react'
// import { RiInformation2Line } from '@remixicon/react'
import type { Plugin, PluginManifestInMarket } from '../../../types'
import Card from '../../../card'
import { pluginManifestInMarketToPluginProps } from '../../utils'
import Button from '@/app/components/base/button'
import { useTranslation } from 'react-i18next'
import { RiLoader2Line } from '@remixicon/react'
import Badge, { BadgeState } from '@/app/components/base/badge/index'
import { useInstallPackageFromMarketPlace } from '@/service/use-plugins'
import { useInstallPackageFromMarketPlace, useUpdatePackageFromMarketPlace } from '@/service/use-plugins'
import checkTaskStatus from '../../base/check-task-status'
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
import Version from '../../base/version'
const i18nPrefix = 'plugin.installModal'
@ -32,13 +33,29 @@ const Installed: FC<Props> = ({
onFailed,
}) => {
const { t } = useTranslation()
const toInstallVersion = payload.version || payload.latest_version
const pluginId = (payload as Plugin).plugin_id
const { installedInfo, isLoading } = useCheckInstalled({
pluginIds: [pluginId],
enabled: !!pluginId,
})
const installedInfoPayload = installedInfo?.[pluginId]
const installedVersion = installedInfoPayload?.installedVersion
const hasInstalled = !!installedVersion
const { mutateAsync: installPackageFromMarketPlace } = useInstallPackageFromMarketPlace()
const { mutateAsync: updatePackageFromMarketPlace } = useUpdatePackageFromMarketPlace()
const [isInstalling, setIsInstalling] = React.useState(false)
const {
check,
stop,
} = checkTaskStatus()
useEffect(() => {
if (hasInstalled && toInstallVersion === installedVersion)
onInstalled()
}, [hasInstalled, toInstallVersion, installedVersion])
const handleCancel = () => {
stop()
onCancel()
@ -50,10 +67,28 @@ const Installed: FC<Props> = ({
setIsInstalling(true)
try {
const {
all_installed: isInstalled,
task_id: taskId,
} = await installPackageFromMarketPlace(uniqueIdentifier)
let taskId
let isInstalled
if (hasInstalled) {
const {
all_installed,
task_id,
} = await updatePackageFromMarketPlace({
original_plugin_unique_identifier: installedInfoPayload.uniqueIdentifier,
new_plugin_unique_identifier: uniqueIdentifier,
})
taskId = task_id
isInstalled = all_installed
}
else {
const {
all_installed,
task_id,
} = await installPackageFromMarketPlace(uniqueIdentifier)
taskId = task_id
isInstalled = all_installed
}
if (isInstalled) {
onInstalled()
return
@ -73,29 +108,6 @@ const Installed: FC<Props> = ({
}
}
const toInstallVersion = '1.3.0'
const supportCheckInstalled = false // TODO: check installed in beta version.
const versionInfo = useMemo(() => {
return (<>{
payload.latest_version === toInstallVersion || !supportCheckInstalled
? (
<Badge className='mx-1' size="s" state={BadgeState.Default}>{payload.version || payload.latest_version}</Badge>
)
: (
<>
<Badge className='mx-1' size="s" state={BadgeState.Warning}>
{`${payload.latest_version} -> ${toInstallVersion}`}
</Badge>
<div className='flex px-0.5 justify-center items-center gap-0.5'>
<div className='text-text-warning system-xs-medium'>Used in 3 apps</div>
<RiInformation2Line className='w-4 h-4 text-text-tertiary' />
</div>
</>
)
}</>)
}, [payload.latest_version, payload.version, supportCheckInstalled])
return (
<>
<div className='flex flex-col px-6 py-3 justify-center items-start gap-4 self-stretch'>
@ -106,7 +118,11 @@ const Installed: FC<Props> = ({
<Card
className='w-full'
payload={pluginManifestInMarketToPluginProps(payload as PluginManifestInMarket)}
titleLeft={versionInfo}
titleLeft={!isLoading && <Version
hasInstalled={hasInstalled}
installedVersion={installedVersion}
toInstallVersion={toInstallVersion}
/>}
/>
</div>
</div>
@ -120,7 +136,7 @@ const Installed: FC<Props> = ({
<Button
variant='primary'
className='min-w-[72px] flex space-x-0.5'
disabled={isInstalling}
disabled={isInstalling || isLoading}
onClick={handleInstall}
>
{isInstalling && <RiLoader2Line className='w-4 h-4 animate-spin-slow' />}

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useAppContext } from '@/context/app-context'
import Button from '@/app/components/base/button'
@ -7,9 +7,9 @@ import Indicator from '@/app/components/header/indicator'
import ToolItem from '@/app/components/tools/provider/tool-item'
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
import {
useBuiltinProviderInfo,
useAllToolProviders,
useBuiltinTools,
useInvalidateBuiltinProviderInfo,
useInvalidateAllToolProviders,
useRemoveProviderCredentials,
useUpdateProviderCredentials,
} from '@/service/use-tools'
@ -26,14 +26,17 @@ const ActionList = ({
const { isCurrentWorkspaceManager } = useAppContext()
const providerBriefInfo = detail.declaration.tool.identity
const providerKey = `${detail.plugin_id}/${providerBriefInfo.name}`
const { data: provider } = useBuiltinProviderInfo(providerKey)
const invalidateProviderInfo = useInvalidateBuiltinProviderInfo()
const { data: collectionList = [] } = useAllToolProviders()
const invalidateAllToolProviders = useInvalidateAllToolProviders()
const provider = useMemo(() => {
return collectionList.find(collection => collection.name === providerKey)
}, [collectionList, providerKey])
const { data } = useBuiltinTools(providerKey)
const [showSettingAuth, setShowSettingAuth] = useState(false)
const handleCredentialSettingUpdate = () => {
invalidateProviderInfo(providerKey)
invalidateAllToolProviders()
Toast.notify({
type: 'success',
message: t('common.api.actionSuccess'),

View File

@ -34,6 +34,7 @@ const ConfigCredential: FC<Props> = ({
const [credentialSchema, setCredentialSchema] = useState<any>(null)
const { name: collectionName } = collection
const [tempCredential, setTempCredential] = React.useState<any>({})
const [isLoading, setIsLoading] = React.useState(false)
useEffect(() => {
fetchBuiltInToolCredentialSchema(collectionName).then(async (res) => {
const toolCredentialSchemas = toolCredentialToFormSchemas(res)
@ -45,14 +46,16 @@ const ConfigCredential: FC<Props> = ({
})
}, [])
const handleSave = () => {
const handleSave = async () => {
for (const field of credentialSchema) {
if (field.required && !tempCredential[field.name]) {
Toast.notify({ type: 'error', message: t('common.errorMsg.fieldRequired', { field: field.label[language] || field.label.en_US }) })
return
}
}
onSaved(tempCredential)
setIsLoading(true)
await onSaved(tempCredential)
setIsLoading(false)
}
return (
@ -102,7 +105,7 @@ const ConfigCredential: FC<Props> = ({
}
< div className='flex space-x-2'>
<Button onClick={onCancel}>{t('common.operation.cancel')}</Button>
<Button variant='primary' onClick={handleSave}>{t('common.operation.save')}</Button>
<Button loading={isLoading} disabled={isLoading} variant='primary' onClick={handleSave}>{t('common.operation.save')}</Button>
</div>
</div>
</>

View File

@ -117,7 +117,7 @@ const translation = {
qualified: 'High Quality',
recommend: 'Recommend',
qualifiedTip: 'Call default system embedding interface for processing to provide higher accuracy when users query.',
warning: 'Please set up the model provider API key first.',
warning: 'Please set up the default embedding model first.',
click: 'Go to settings',
economical: 'Economical',
economicalTip: 'Use offline vector engines, keyword indexes, etc. to reduce accuracy without spending tokens',

View File

@ -117,7 +117,7 @@ const translation = {
qualified: '高质量',
recommend: '推荐',
qualifiedTip: '调用系统默认的嵌入接口进行处理,以在用户查询时提供更高的准确度',
warning: '请先完成模型供应商的 API KEY 设置。.',
warning: '请先设置默认 embedding 模型',
click: '前往设置',
economical: '经济',
economicalTip: '使用离线的向量引擎、关键词索引等方式,降低了准确度但无需花费 Token',

View File

@ -45,6 +45,8 @@ const afterResponseErrorCode = (otherOptions: IOtherOptions): AfterResponseHook
globalThis.location.href = `${globalThis.location.origin}/signin`
})
break
case 401:
return Promise.reject(response)
// fall through
default:
bodyJson.then((data: ResponseError) => {

View File

@ -8,6 +8,7 @@ import type {
PackageDependency,
Permissions,
Plugin,
PluginDetail,
PluginTask,
PluginsFromMarketplaceByInfoResponse,
PluginsFromMarketplaceResponse,
@ -29,6 +30,25 @@ import { useInvalidateAllBuiltInTools } from './use-tools'
const NAME_SPACE = 'plugins'
const useInstalledPluginListKey = [NAME_SPACE, 'installedPluginList']
export const useCheckInstalled = ({
pluginIds,
enabled,
}: {
pluginIds: string[],
enabled: boolean
}) => {
return useQuery<{ plugins: PluginDetail[] }>({
queryKey: [NAME_SPACE, 'checkInstalled'],
queryFn: () => post<{ plugins: PluginDetail[] }>('/workspaces/current/plugin/list/installations/ids', {
body: {
plugin_ids: pluginIds,
},
}),
enabled,
staleTime: 0, // always fresh
})
}
export const useInstalledPluginList = (disable?: boolean) => {
return useQuery<InstalledPluginListResponse>({
queryKey: useInstalledPluginListKey,
@ -58,6 +78,16 @@ export const useInstallPackageFromMarketPlace = () => {
})
}
export const useUpdatePackageFromMarketPlace = () => {
return useMutation({
mutationFn: (body: object) => {
return post<InstallPackageResponse>('/workspaces/current/plugin/upgrade/marketplace', {
body,
})
},
})
}
export const useVersionListOfPlugin = (pluginID: string) => {
return useQuery<{ data: VersionListResponse }>({
queryKey: [NAME_SPACE, 'versions', pluginID],

View File

@ -15,7 +15,7 @@ const NAME_SPACE = 'tools'
const useAllToolProvidersKey = [NAME_SPACE, 'allToolProviders']
export const useAllToolProviders = () => {
return useQuery({
return useQuery<Collection[]>({
queryKey: useAllToolProvidersKey,
queryFn: () => get<Collection[]>('/workspaces/current/tool-providers'),
})