Merge branch 'feat/plugins' of https://github.com/langgenius/dify into feat/plugins

This commit is contained in:
AkaraChen 2024-12-25 15:23:50 +08:00
commit b8e4580074
4 changed files with 236 additions and 18 deletions

View File

@ -1,10 +1,9 @@
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import ToolItem from '@/app/components/tools/provider/tool-item'
import StrategyItem from '@/app/components/plugins/plugin-detail-panel/strategy-item'
import {
useAllToolProviders,
useBuiltinTools,
} from '@/service/use-tools'
useStrategyProviderDetail,
} from '@/service/use-strategy'
import type { PluginDetail } from '@/app/components/plugins/types'
type Props = {
@ -17,31 +16,31 @@ const AgentStrategyList = ({
const { t } = useTranslation()
const providerBriefInfo = detail.declaration.agent_strategy.identity
const providerKey = `${detail.plugin_id}/${providerBriefInfo.name}`
const { data: collectionList = [] } = useAllToolProviders()
const { data: strategyProviderDetail } = useStrategyProviderDetail(providerKey)
const provider = useMemo(() => {
return collectionList.find(collection => collection.name === providerKey)
}, [collectionList, providerKey])
const { data } = useBuiltinTools(providerKey)
const strategyList = useMemo(() => {
if (!strategyProviderDetail)
return []
if (!data || !provider)
return strategyProviderDetail.declaration.strategies
}, [strategyProviderDetail])
if (!strategyProviderDetail)
return null
return (
<div className='px-4 pt-2 pb-4'>
<div className='mb-1 py-1'>
<div className='mb-1 h-6 flex items-center justify-between text-text-secondary system-sm-semibold-uppercase'>
{t('plugin.detailPanel.strategyNum', { num: data.length, strategy: data.length > 1 ? 'strategies' : 'strategy' })}
{t('plugin.detailPanel.strategyNum', { num: strategyList.length, strategy: strategyList.length > 1 ? 'strategies' : 'strategy' })}
</div>
</div>
<div className='flex flex-col gap-2'>
{data.map(tool => (
<ToolItem
key={`${detail.plugin_id}${tool.name}`}
collection={provider}
tool={tool}
isBuiltIn
isModel={false}
{strategyList.map(strategyDetail => (
<StrategyItem
key={`${strategyDetail.identity.provider}${strategyDetail.identity.name}`}
provider={strategyProviderDetail.declaration.identity}
detail={strategyDetail}
/>
))}
</div>

View File

@ -0,0 +1,165 @@
'use client'
import type { FC } from 'react'
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import {
RiArrowLeftLine,
RiCloseLine,
} from '@remixicon/react'
import Drawer from '@/app/components/base/drawer'
import ActionButton from '@/app/components/base/action-button'
import Icon from '@/app/components/plugins/card/base/card-icon'
import Description from '@/app/components/plugins/card/base/description'
import Divider from '@/app/components/base/divider'
import type {
StrategyDetail,
} from '@/app/components/plugins/types'
import type { Locale } from '@/i18n'
import I18n from '@/context/i18n'
import { getLanguage } from '@/i18n/language'
import cn from '@/utils/classnames'
type Props = {
provider: {
author: string
name: string
description: Record<Locale, string>
icon: string
label: Record<Locale, string>
tags: string[]
}
detail: StrategyDetail
onHide: () => void
}
const StrategyDetail: FC<Props> = ({
provider,
detail,
onHide,
}) => {
const { locale } = useContext(I18n)
const language = getLanguage(locale)
const { t } = useTranslation()
const outputSchema = useMemo(() => {
const res: any[] = []
if (!detail.output_schema)
return []
Object.keys(detail.output_schema.properties).forEach((outputKey) => {
const output = detail.output_schema.properties[outputKey]
res.push({
name: outputKey,
type: output.type === 'array'
? `Array[${output.items?.type.slice(0, 1).toLocaleUpperCase()}${output.items?.type.slice(1)}]`
: `${output.type.slice(0, 1).toLocaleUpperCase()}${output.type.slice(1)}`,
description: output.description,
})
})
return res
}, [detail.output_schema])
const getType = (type: string) => {
if (type === 'number-input')
return t('tools.setBuiltInTools.number')
if (type === 'text-input')
return t('tools.setBuiltInTools.string')
if (type === 'file')
return t('tools.setBuiltInTools.file')
if (type === 'array[tools]')
return 'multiple-tool-select'
return type
}
return (
<Drawer
isOpen
clickOutsideNotOpen={false}
onClose={onHide}
footer={null}
mask={false}
positionCenter={false}
panelClassname={cn('justify-start mt-[64px] mr-2 mb-2 !w-[420px] !max-w-[420px] !p-0 !bg-components-panel-bg rounded-2xl border-[0.5px] border-components-panel-border shadow-xl')}
>
<>
{/* header */}
<div className='relative p-4 pb-3 border-b border-divider-subtle'>
<div className='absolute top-3 right-3'>
<ActionButton onClick={onHide}>
<RiCloseLine className='w-4 h-4' />
</ActionButton>
</div>
<div
className='mb-2 flex items-center gap-1 text-text-accent-secondary system-xs-semibold-uppercase cursor-pointer'
onClick={onHide}
>
<RiArrowLeftLine className='w-4 h-4' />
BACK
</div>
<div className='flex items-center gap-1'>
<Icon size='tiny' className='w-6 h-6' src={provider.icon} />
<div className=''>{provider.label[language]}</div>
</div>
<div className='mt-1 text-text-primary system-md-semibold'>{detail.identity.label[language]}</div>
<Description className='mt-3' text={detail.description[language]} descriptionLineRows={2}></Description>
</div>
{/* form */}
<div className='h-full'>
<div className='flex flex-col h-full overflow-y-auto'>
<div className='p-4 pb-1 text-text-primary system-sm-semibold-uppercase'>{t('tools.setBuiltInTools.parameters')}</div>
<div className='px-4'>
{detail.parameters.length > 0 && (
<div className='py-2 space-y-1'>
{detail.parameters.map((item: any, index) => (
<div key={index} className='py-1'>
<div className='flex items-center gap-2'>
<div className='text-text-secondary code-sm-semibold'>{item.label[language]}</div>
<div className='text-text-tertiary system-xs-regular'>
{getType(item.type)}
</div>
{item.required && (
<div className='text-text-warning-secondary system-xs-medium'>{t('tools.setBuiltInTools.required')}</div>
)}
</div>
{item.human_description && (
<div className='mt-0.5 text-text-tertiary system-xs-regular'>
{item.human_description?.[language]}
</div>
)}
</div>
))}
</div>
)}
</div>
{detail.output_schema && (
<>
<div className='px-4'>
<Divider className="!mt-2" />
</div>
<div className='p-4 pb-1 text-text-primary system-sm-semibold-uppercase'>OUTPUT</div>
{outputSchema.length > 0 && (
<div className='py-2 space-y-1'>
{outputSchema.map((outputItem, index) => (
<div key={index} className='py-1'>
<div className='flex items-center gap-2'>
<div className='text-text-secondary code-sm-semibold'>{outputItem.name}</div>
<div className='text-text-tertiary system-xs-regular'>{outputItem.type}</div>
</div>
{outputItem.description && (
<div className='mt-0.5 text-text-tertiary system-xs-regular'>
{outputItem.description}
</div>
)}
</div>
))}
</div>
)}
</>
)}
</div>
</div>
</>
</Drawer>
)
}
export default StrategyDetail

View File

@ -0,0 +1,52 @@
'use client'
import React, { useState } from 'react'
import { useContext } from 'use-context-selector'
import StrategyDetailPanel from './strategy-detail'
import type {
StrategyDetail,
} from '@/app/components/plugins/types'
import type { Locale } from '@/i18n'
import I18n from '@/context/i18n'
import { getLanguage } from '@/i18n/language'
import cn from '@/utils/classnames'
type Props = {
provider: {
author: string
name: string
description: Record<Locale, string>
icon: string
label: Record<Locale, string>
tags: string[]
}
detail: StrategyDetail
}
const StrategyItem = ({
provider,
detail,
}: Props) => {
const { locale } = useContext(I18n)
const language = getLanguage(locale)
const [showDetail, setShowDetail] = useState(false)
return (
<>
<div
className={cn('mb-2 px-4 py-3 bg-components-panel-item-bg rounded-xl border-[0.5px] border-components-panel-border-subtle shadow-xs cursor-pointer hover:bg-components-panel-on-panel-item-bg-hover')}
onClick={() => setShowDetail(true)}
>
<div className='pb-0.5 text-text-secondary system-md-semibold'>{detail.identity.label[language]}</div>
<div className='text-text-tertiary system-xs-regular line-clamp-2' title={detail.description[language]}>{detail.description[language]}</div>
</div>
{showDetail && (
<StrategyDetailPanel
provider={provider}
detail={detail}
onHide={() => setShowDetail(false)}
/>
)}
</>
)
}
export default StrategyItem

View File

@ -379,6 +379,8 @@ export type VersionProps = {
export type StrategyParamItem = {
name: string
label: Record<Locale, string>
human_description: Record<Locale, string>
llm_description: string
placeholder: Record<Locale, string>
type: string
scope: string