mirror of https://github.com/langgenius/dify.git
Merge branch 'feat/plugins' of https://github.com/langgenius/dify into feat/plugins
This commit is contained in:
commit
cd7e6ca010
|
|
@ -0,0 +1,27 @@
|
|||
import Button from '../button'
|
||||
import { RiInstallLine, RiLoader2Line } from '@remixicon/react'
|
||||
|
||||
type InstallButtonProps = {
|
||||
loading: boolean
|
||||
onInstall: () => void
|
||||
t: any
|
||||
}
|
||||
|
||||
const InstallButton = ({ loading, onInstall, t }: InstallButtonProps) => {
|
||||
return (
|
||||
<Button size='small' className='z-[100]' onClick={onInstall}>
|
||||
<div className={`flex px-[3px] justify-center items-center gap-1
|
||||
${loading ? 'text-components-button-secondary-text-disabled' : 'text-components-button-secondary-text'}
|
||||
system-xs-medium`}
|
||||
>
|
||||
{loading ? t('workflow.nodes.agent.pluginInstaller.installing') : t('workflow.nodes.agent.pluginInstaller.install')}
|
||||
</div>
|
||||
{loading
|
||||
? <RiLoader2Line className='w-3.5 h-3.5 text-text-quaternary' />
|
||||
: <RiInstallLine className='w-3.5 h-3.5 text-text-secondary' />
|
||||
}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default InstallButton
|
||||
|
|
@ -4,7 +4,7 @@ import type {
|
|||
ModelProvider,
|
||||
} from '../declarations'
|
||||
import { useLanguage } from '../hooks'
|
||||
import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
|
||||
import { Group } from '@/app/components/base/icons/src/vender/other'
|
||||
import { OpenaiBlue, OpenaiViolet } from '@/app/components/base/icons/src/public/llm'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
|
|
@ -41,10 +41,12 @@ const ModelIcon: FC<ModelIconProps> = ({
|
|||
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex items-center justify-center w-6 h-6 rounded border-[0.5px] border-black/5 bg-gray-50',
|
||||
'flex items-center justify-center w-5 h-5 rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-subtle',
|
||||
className,
|
||||
)}>
|
||||
<CubeOutline className='w-4 h-4 text-text-quaternary' />
|
||||
<div className='flex w-3 h-3 items-center justify-center opacity-35'>
|
||||
<Group className='text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { FC } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type {
|
||||
CustomConfigurationModelFixedFields,
|
||||
|
|
@ -10,20 +11,24 @@ import {
|
|||
CustomConfigurationStatusEnum,
|
||||
} from '../declarations'
|
||||
import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from '../provider-added-card'
|
||||
import { ModelStatusEnum } from '../declarations'
|
||||
import type { PluginInfoFromMarketPlace } from '@/app/components/plugins/types'
|
||||
import { useInstallPackageFromMarketPlace } from '@/service/use-plugins'
|
||||
import ConfigurationButton from './configuration-button'
|
||||
import { PluginType } from '@/app/components/plugins/types'
|
||||
import {
|
||||
useUpdateModelList,
|
||||
useUpdateModelProviders,
|
||||
} from '../hooks'
|
||||
import ModelIcon from '../model-icon'
|
||||
import ModelName from '../model-name'
|
||||
import Button from '@/app/components/base/button'
|
||||
import ModelDisplay from './model-display'
|
||||
import InstallButton from '@/app/components/base/install-button'
|
||||
import StatusIndicators from './status-indicators'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { RiEqualizer2Line, RiErrorWarningFill } from '@remixicon/react'
|
||||
import { RiEqualizer2Line } from '@remixicon/react'
|
||||
import { fetchPluginInfoFromMarketPlace } from '@/service/plugins'
|
||||
|
||||
export type AgentModelTriggerProps = {
|
||||
open?: boolean
|
||||
|
|
@ -56,6 +61,36 @@ const AgentModelTrigger: FC<AgentModelTriggerProps> = ({
|
|||
item => item.quota_type === modelProvider.system_configuration.current_quota_type,
|
||||
)
|
||||
)
|
||||
const [pluginInfo, setPluginInfo] = useState<PluginInfoFromMarketPlace | null>(null)
|
||||
const [isPluginChecked, setIsPluginChecked] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [installed, setInstalled] = useState(false)
|
||||
const { mutateAsync: installPackageFromMarketPlace } = useInstallPackageFromMarketPlace()
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (providerName && !modelProvider) {
|
||||
const parts = providerName.split('/')
|
||||
const org = parts[0]
|
||||
const name = parts[1]
|
||||
try {
|
||||
const pluginInfo = await fetchPluginInfoFromMarketPlace({ org, name })
|
||||
if (pluginInfo.data.plugin.category === PluginType.model)
|
||||
setPluginInfo(pluginInfo.data.plugin)
|
||||
}
|
||||
catch (error) {
|
||||
// pass
|
||||
}
|
||||
setIsPluginChecked(true)
|
||||
}
|
||||
else {
|
||||
setIsPluginChecked(true)
|
||||
}
|
||||
})()
|
||||
}, [providerName, modelProvider])
|
||||
|
||||
if (modelId && !isPluginChecked)
|
||||
return null
|
||||
|
||||
const handleOpenModal = (
|
||||
provider: ModelProvider,
|
||||
|
|
@ -97,64 +132,41 @@ const AgentModelTrigger: FC<AgentModelTriggerProps> = ({
|
|||
>
|
||||
{modelId ? (
|
||||
<>
|
||||
{currentProvider && (
|
||||
<ModelIcon
|
||||
className="m-0.5"
|
||||
provider={currentProvider}
|
||||
modelName={currentModel?.model}
|
||||
isDeprecated={hasDeprecated}
|
||||
/>
|
||||
)}
|
||||
{!currentProvider && (
|
||||
<ModelIcon
|
||||
className="m-0.5"
|
||||
provider={modelProvider}
|
||||
modelName={modelId}
|
||||
isDeprecated={hasDeprecated}
|
||||
/>
|
||||
)}
|
||||
{currentModel && (
|
||||
<ModelName
|
||||
className="flex px-1 py-[3px] items-center gap-1 grow"
|
||||
modelItem={currentModel}
|
||||
showMode
|
||||
showFeatures
|
||||
/>
|
||||
)}
|
||||
{!currentModel && (
|
||||
<div className="flex py-[3px] px-1 items-center gap-1 grow opacity-50 truncate">
|
||||
<div className="text-components-input-text-filled text-ellipsis overflow-hidden system-sm-regular">
|
||||
{modelId}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<ModelIcon
|
||||
className="m-0.5"
|
||||
provider={currentProvider || modelProvider}
|
||||
modelName={currentModel?.model || modelId}
|
||||
isDeprecated={hasDeprecated}
|
||||
/>
|
||||
<ModelDisplay
|
||||
currentModel={currentModel}
|
||||
modelId={modelId}
|
||||
/>
|
||||
{needsConfiguration && (
|
||||
<Button
|
||||
size="small"
|
||||
className="z-[100]"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleOpenModal(modelProvider, ConfigurationMethodEnum.predefinedModel, undefined)
|
||||
}}
|
||||
>
|
||||
<div className="flex px-[3px] justify-center items-center gap-1">
|
||||
{t('workflow.nodes.agent.notAuthorized')}
|
||||
</div>
|
||||
<div className="flex w-[14px] h-[14px] justify-center items-center">
|
||||
<div className="w-2 h-2 shrink-0 rounded-[3px] border border-components-badge-status-light-warning-border-inner
|
||||
bg-components-badge-status-light-warning-bg shadow-components-badge-status-light-warning-halo" />
|
||||
</div>
|
||||
</Button>
|
||||
<ConfigurationButton
|
||||
modelProvider={modelProvider}
|
||||
handleOpenModal={handleOpenModal}
|
||||
/>
|
||||
)}
|
||||
<StatusIndicators
|
||||
needsConfiguration={needsConfiguration}
|
||||
modelProvider={!!modelProvider}
|
||||
disabled={!!disabled}
|
||||
pluginInfo={pluginInfo}
|
||||
t={t}
|
||||
/>
|
||||
{!installed && !modelProvider && pluginInfo && (
|
||||
<InstallButton
|
||||
loading={loading}
|
||||
onInstall={async () => {
|
||||
setLoading(true)
|
||||
const { all_installed } = await installPackageFromMarketPlace(pluginInfo.latest_package_identifier)
|
||||
if (all_installed)
|
||||
setInstalled(true)
|
||||
}}
|
||||
t={t}
|
||||
/>
|
||||
)}
|
||||
{!needsConfiguration && disabled && (
|
||||
<Tooltip
|
||||
popupContent={t('workflow.nodes.agent.modelSelectorTooltips.deprecated')}
|
||||
asChild={false}
|
||||
>
|
||||
<RiErrorWarningFill className='w-4 h-4 text-text-destructive' />
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
|
@ -168,11 +180,6 @@ const AgentModelTrigger: FC<AgentModelTriggerProps> = ({
|
|||
</div>
|
||||
</>
|
||||
)}
|
||||
{currentProvider && currentModel && currentModel.status === ModelStatusEnum.active && (
|
||||
<div className="flex pr-1 items-center">
|
||||
<RiEqualizer2Line className="w-4 h-4 text-text-tertiary group-hover:text-text-secondary" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
import Button from '@/app/components/base/button'
|
||||
import { ConfigurationMethodEnum } from '../declarations'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type ConfigurationButtonProps = {
|
||||
modelProvider: any
|
||||
handleOpenModal: any
|
||||
}
|
||||
|
||||
const ConfigurationButton = ({ modelProvider, handleOpenModal }: ConfigurationButtonProps) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
className="z-[100]"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleOpenModal(modelProvider, ConfigurationMethodEnum.predefinedModel, undefined)
|
||||
}}
|
||||
>
|
||||
<div className="flex px-[3px] justify-center items-center gap-1">
|
||||
{t('workflow.nodes.agent.notAuthorized')}
|
||||
</div>
|
||||
<div className="flex w-[14px] h-[14px] justify-center items-center">
|
||||
<div className="w-2 h-2 shrink-0 rounded-[3px] border border-components-badge-status-light-warning-border-inner
|
||||
bg-components-badge-status-light-warning-bg shadow-components-badge-status-light-warning-halo" />
|
||||
</div>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigurationButton
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import ModelName from '../model-name'
|
||||
|
||||
type ModelDisplayProps = {
|
||||
currentModel: any
|
||||
modelId: string
|
||||
}
|
||||
|
||||
const ModelDisplay = ({ currentModel, modelId }: ModelDisplayProps) => {
|
||||
return currentModel ? (
|
||||
<ModelName
|
||||
className="flex px-1 py-[3px] items-center gap-1 grow"
|
||||
modelItem={currentModel}
|
||||
showMode
|
||||
showFeatures
|
||||
/>
|
||||
) : (
|
||||
<div className="flex py-[3px] px-1 items-center gap-1 grow opacity-50 truncate">
|
||||
<div className="text-components-input-text-filled text-ellipsis overflow-hidden system-sm-regular">
|
||||
{modelId}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelDisplay
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { RiErrorWarningFill } from '@remixicon/react'
|
||||
|
||||
type StatusIndicatorsProps = {
|
||||
needsConfiguration: boolean
|
||||
modelProvider: boolean
|
||||
disabled: boolean
|
||||
pluginInfo: any
|
||||
t: any
|
||||
}
|
||||
|
||||
const StatusIndicators = ({ needsConfiguration, modelProvider, disabled, pluginInfo, t }: StatusIndicatorsProps) => {
|
||||
return (
|
||||
<>
|
||||
{!needsConfiguration && modelProvider && disabled && (
|
||||
<Tooltip
|
||||
popupContent={t('workflow.nodes.agent.modelSelectorTooltips.deprecated')}
|
||||
asChild={false}
|
||||
>
|
||||
<RiErrorWarningFill className='w-4 h-4 text-text-destructive' />
|
||||
</Tooltip>
|
||||
)}
|
||||
{!modelProvider && !pluginInfo && (
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='flex w-[240px] max-w-[240px] gap-1 flex-col px-1 py-1.5'>
|
||||
<div className='text-text-primary title-xs-semi-bold'>{t('workflow.nodes.agent.modelNotInMarketplace.title')}</div>
|
||||
<div className='min-w-[200px] text-text-secondary body-xs-regular'>
|
||||
{t('workflow.nodes.agent.modelNotInMarketplace.desc')}
|
||||
</div>
|
||||
<div className='text-text-accent body-xs-regular'>{t('workflow.nodes.agent.modelNotInMarketplace.manageInPlugins')}</div>
|
||||
</div>
|
||||
}
|
||||
asChild={false}
|
||||
needsDelay
|
||||
>
|
||||
<RiErrorWarningFill className='w-4 h-4 text-text-destructive' />
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default StatusIndicators
|
||||
|
|
@ -81,7 +81,7 @@ export type PluginManifestInMarket = {
|
|||
icon: string
|
||||
label: Record<Locale, string>
|
||||
category: PluginType
|
||||
version: string // conbine the other place to it
|
||||
version: string // combine the other place to it
|
||||
latest_version: string
|
||||
brief: Record<Locale, string>
|
||||
introduction: string
|
||||
|
|
@ -108,6 +108,11 @@ export type PluginDetail = {
|
|||
meta?: MetaData
|
||||
}
|
||||
|
||||
export type PluginInfoFromMarketPlace = {
|
||||
category: PluginType
|
||||
latest_package_identifier: string
|
||||
}
|
||||
|
||||
export type Plugin = {
|
||||
type: 'plugin' | 'bundle' | 'model' | 'extension' | 'tool' | 'agent_strategy'
|
||||
org: string
|
||||
|
|
|
|||
|
|
@ -714,6 +714,11 @@ const translation = {
|
|||
install: 'Install',
|
||||
installing: 'Installing',
|
||||
},
|
||||
modelNotInMarketplace: {
|
||||
title: 'Model not installed',
|
||||
desc: 'This model is not installed from the marketplace. Please go to Plugins to reinstall.',
|
||||
manageInPlugins: 'Manage in Plugins',
|
||||
},
|
||||
configureModel: 'Configure Model',
|
||||
notAuthorized: 'Not Authorized',
|
||||
model: 'model',
|
||||
|
|
|
|||
|
|
@ -714,6 +714,11 @@ const translation = {
|
|||
install: '安装',
|
||||
installing: '安装中',
|
||||
},
|
||||
modelNotInMarketplace: {
|
||||
title: '模型未安装',
|
||||
desc: '此模型未从市场安装。请转到插件重新安装。',
|
||||
manageInPlugins: '在插件中管理',
|
||||
},
|
||||
model: '模型',
|
||||
toolbox: '工具箱',
|
||||
strategyNotSet: '代理策略未设置',
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import type {
|
|||
InstallPackageResponse,
|
||||
Permissions,
|
||||
PluginDeclaration,
|
||||
PluginInfoFromMarketPlace,
|
||||
PluginManifestInMarket,
|
||||
PluginTasksResponse,
|
||||
TaskStatusResponse,
|
||||
|
|
@ -75,6 +76,13 @@ export const fetchBundleInfoFromMarketPlace = async ({
|
|||
return getMarketplace<{ data: { version: { dependencies: Dependency[] } } }>(`/bundles/${org}/${name}/${version}`)
|
||||
}
|
||||
|
||||
export const fetchPluginInfoFromMarketPlace = async ({
|
||||
org,
|
||||
name,
|
||||
}: Record<string, string>) => {
|
||||
return getMarketplace<{ data: { plugin: PluginInfoFromMarketPlace, version: { version: string } } }>(`/plugins/${org}/${name}`)
|
||||
}
|
||||
|
||||
export const fetchMarketplaceCollections: Fetcher<MarketplaceCollectionsResponse, { url: string; }> = ({ url }) => {
|
||||
return get<MarketplaceCollectionsResponse>(url)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue