Merge branch 'feat/plugins' of https://github.com/langgenius/dify into feat/plugins
This commit is contained in:
commit
7d4f8e0082
@ -9,7 +9,7 @@ const Line = ({
|
||||
<path d="M1 0.5L1 240.5" stroke="url(#paint0_linear_1989_74474)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1989_74474" x1="-7.99584" y1="240.5" x2="-7.88094" y2="0.50004" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stopOpacity="0.01"/>
|
||||
<stop stopColor="white" stopOpacity="0.01"/>
|
||||
<stop offset="0.503965" stopColor="#101828" stopOpacity="0.08"/>
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.01"/>
|
||||
</linearGradient>
|
||||
|
@ -3,28 +3,68 @@ import {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import type { MarketplaceCollection } from '@/app/components/plugins/marketplace/types'
|
||||
import { getMarketplaceCollectionsAndPlugins } from '@/app/components/plugins/marketplace/utils'
|
||||
import type {
|
||||
MarketplaceCollection,
|
||||
PluginsSearchParams,
|
||||
} from '@/app/components/plugins/marketplace/types'
|
||||
import {
|
||||
getMarketplaceCollectionsAndPlugins,
|
||||
getMarketplacePlugins,
|
||||
} from '@/app/components/plugins/marketplace/utils'
|
||||
|
||||
export const useMarketplace = () => {
|
||||
export const useMarketplace = (searchPluginText: string, filterPluginTags: string[]) => {
|
||||
const [marketplaceCollections, setMarketplaceCollections] = useState<MarketplaceCollection[]>([])
|
||||
const [marketplaceCollectionPluginsMap, setMarketplaceCollectionPluginsMap] = useState<Record<string, Plugin[]>>({})
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const getMarketplaceCollections = useCallback(async () => {
|
||||
const [plugins, setPlugins] = useState<Plugin[]>()
|
||||
|
||||
const handleUpldateMarketplaceCollections = useCallback(async () => {
|
||||
setIsLoading(true)
|
||||
const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins()
|
||||
setIsLoading(false)
|
||||
|
||||
setMarketplaceCollections(marketplaceCollections)
|
||||
setMarketplaceCollectionPluginsMap(marketplaceCollectionPluginsMap)
|
||||
setPlugins(undefined)
|
||||
}, [])
|
||||
|
||||
const handleUpdatePlugins = async (query: PluginsSearchParams) => {
|
||||
setIsLoading(true)
|
||||
const { marketplacePlugins } = await getMarketplacePlugins(query)
|
||||
setIsLoading(false)
|
||||
|
||||
setPlugins(marketplacePlugins)
|
||||
}
|
||||
|
||||
const { run: handleUpdatePluginsWithDebounced } = useDebounceFn(handleUpdatePlugins, {
|
||||
wait: 500,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
getMarketplaceCollections()
|
||||
}, [getMarketplaceCollections])
|
||||
if (searchPluginText || filterPluginTags.length) {
|
||||
if (searchPluginText) {
|
||||
handleUpdatePluginsWithDebounced({
|
||||
query: searchPluginText,
|
||||
tags: filterPluginTags,
|
||||
})
|
||||
return
|
||||
}
|
||||
handleUpdatePlugins({
|
||||
query: searchPluginText,
|
||||
tags: filterPluginTags,
|
||||
})
|
||||
}
|
||||
else {
|
||||
handleUpldateMarketplaceCollections()
|
||||
}
|
||||
}, [searchPluginText, filterPluginTags, handleUpdatePluginsWithDebounced, handleUpldateMarketplaceCollections])
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
marketplaceCollections,
|
||||
marketplaceCollectionPluginsMap,
|
||||
plugins,
|
||||
}
|
||||
}
|
||||
|
@ -4,16 +4,21 @@ import List from '@/app/components/plugins/marketplace/list'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
|
||||
type MarketplaceProps = {
|
||||
searchPluginText: string
|
||||
filterPluginTags: string[]
|
||||
onMarketplaceScroll: () => void
|
||||
}
|
||||
const Marketplace = ({
|
||||
searchPluginText,
|
||||
filterPluginTags,
|
||||
onMarketplaceScroll,
|
||||
}: MarketplaceProps) => {
|
||||
const {
|
||||
isLoading,
|
||||
marketplaceCollections,
|
||||
marketplaceCollectionPluginsMap,
|
||||
} = useMarketplace()
|
||||
plugins,
|
||||
} = useMarketplace(searchPluginText, filterPluginTags)
|
||||
|
||||
return (
|
||||
<div className='shrink-0 sticky -bottom-[442px] h-[530px] overflow-y-auto px-12 py-2 pt-0 bg-background-default-subtle'>
|
||||
@ -55,6 +60,7 @@ const Marketplace = ({
|
||||
<List
|
||||
marketplaceCollections={marketplaceCollections}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
|
||||
plugins={plugins}
|
||||
showInstallButton
|
||||
/>
|
||||
)
|
||||
|
@ -126,9 +126,13 @@ const ProviderList = () => {
|
||||
</div>
|
||||
{
|
||||
enable_marketplace && (
|
||||
<Marketplace onMarketplaceScroll={() => {
|
||||
containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'smooth' })
|
||||
}} />
|
||||
<Marketplace
|
||||
onMarketplaceScroll={() => {
|
||||
containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'smooth' })
|
||||
}}
|
||||
searchPluginText={keywords}
|
||||
filterPluginTags={tagFilterValue}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
@ -3,6 +3,8 @@ import type { FC, RefObject } from 'react'
|
||||
import type { ToolWithProvider } from '../types'
|
||||
import { CollectionType } from '../../tools/types'
|
||||
|
||||
export const CUSTOM_GROUP_NAME = '@@@custom@@@'
|
||||
export const WORKFLOW_GROUP_NAME = '@@@workflow@@@'
|
||||
/*
|
||||
{
|
||||
A: {
|
||||
@ -42,9 +44,9 @@ export const groupItems = (items: ToolWithProvider[], getFirstChar: (item: ToolW
|
||||
if (item.type === CollectionType.builtIn)
|
||||
groupName = item.author
|
||||
else if (item.type === CollectionType.custom)
|
||||
groupName = 'custom'
|
||||
groupName = CUSTOM_GROUP_NAME
|
||||
else
|
||||
groupName = 'workflow'
|
||||
groupName = WORKFLOW_GROUP_NAME
|
||||
|
||||
if (!acc[letter][groupName])
|
||||
acc[letter][groupName] = []
|
||||
@ -76,7 +78,7 @@ const IndexBar: FC<IndexBarProps> = ({ letters, itemRefs }) => {
|
||||
element.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
return (
|
||||
<div className="index-bar absolute right-4 top-36 flex flex-col items-center w-6 justify-center text-xs font-medium text-text-quaternary ">
|
||||
<div className="index-bar absolute right-0 top-36 flex flex-col items-center w-6 justify-center text-xs font-medium text-text-quaternary ">
|
||||
<div className='absolute left-0 top-0 h-full w-px bg-[linear-gradient(270deg,rgba(255,255,255,0)_0%,rgba(16,24,40,0.08)_30%,rgba(16,24,40,0.08)_50%,rgba(16,24,40,0.08)_70.5%,rgba(255,255,255,0)_100%)]'></div>
|
||||
{letters.map(letter => (
|
||||
<div className="hover:text-text-secondary cursor-pointer" key={letter} onClick={() => handleIndexClick(letter)}>
|
||||
|
@ -1,16 +1,36 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import type { ToolWithProvider } from '../../../types'
|
||||
import type { BlockEnum } from '../../../types'
|
||||
import type { ToolDefaultValue } from '../../types'
|
||||
import Tool from '../tool'
|
||||
import { ViewType } from '../../view-type-select'
|
||||
|
||||
type Props = {
|
||||
|
||||
payload: ToolWithProvider[]
|
||||
isShowLetterIndex: boolean
|
||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||
}
|
||||
|
||||
const ToolViewFlatView: FC<Props> = () => {
|
||||
const ToolViewFlatView: FC<Props> = ({
|
||||
payload,
|
||||
isShowLetterIndex,
|
||||
onSelect,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
list...
|
||||
{payload.map(tool => (
|
||||
<Tool
|
||||
key={tool.id}
|
||||
payload={tool}
|
||||
viewType={ViewType.flat}
|
||||
isShowLetterIndex={isShowLetterIndex}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ToolViewFlatView)
|
||||
|
@ -1,20 +1,33 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import type { ToolWithProvider } from '../../../types'
|
||||
import type { BlockEnum } from '../../../types'
|
||||
import type { ToolDefaultValue } from '../../types'
|
||||
import Item from './item'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { CUSTOM_GROUP_NAME, WORKFLOW_GROUP_NAME } from '../../index-bar'
|
||||
|
||||
type Props = {
|
||||
payload: Record<string, ToolWithProvider[]>
|
||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||
}
|
||||
|
||||
const OrgTools: FC<Props> = ({
|
||||
const ToolListTreeView: FC<Props> = ({
|
||||
payload,
|
||||
onSelect,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const getI18nGroupName = useCallback((name: string) => {
|
||||
if (name === CUSTOM_GROUP_NAME)
|
||||
return t('workflow.tabs.customTool')
|
||||
|
||||
if (name === WORKFLOW_GROUP_NAME)
|
||||
return t('workflow.tabs.workflowTool')
|
||||
|
||||
return name
|
||||
}, [t])
|
||||
|
||||
if (!payload) return null
|
||||
|
||||
return (
|
||||
@ -22,7 +35,7 @@ const OrgTools: FC<Props> = ({
|
||||
{Object.keys(payload).map(groupName => (
|
||||
<Item
|
||||
key={groupName}
|
||||
groupName={groupName}
|
||||
groupName={getI18nGroupName(groupName)}
|
||||
toolList={payload[groupName]}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
@ -30,4 +43,5 @@ const OrgTools: FC<Props> = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(OrgTools)
|
||||
|
||||
export default React.memo(ToolListTreeView)
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
@ -13,6 +13,7 @@ import ActonItem from './action-item'
|
||||
import BlockIcon from '../../block-icon'
|
||||
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@ -29,8 +30,9 @@ const Tool: FC<Props> = ({
|
||||
isShowLetterIndex,
|
||||
onSelect,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
const isTreeView = viewType === ViewType.tree
|
||||
const isFlatView = viewType === ViewType.flat
|
||||
const actions = payload.tools
|
||||
const hasAction = payload.type === CollectionType.builtIn
|
||||
const [isFold, {
|
||||
@ -38,10 +40,23 @@ const Tool: FC<Props> = ({
|
||||
}] = useBoolean(false)
|
||||
const FoldIcon = isFold ? RiArrowDownSLine : RiArrowRightSLine
|
||||
|
||||
const groupName = useMemo(() => {
|
||||
if (payload.type === CollectionType.builtIn)
|
||||
return payload.author
|
||||
|
||||
if (payload.type === CollectionType.custom)
|
||||
return t('workflow.tabs.customTool')
|
||||
|
||||
if (payload.type === CollectionType.workflow)
|
||||
return t('workflow.tabs.workflowTool')
|
||||
|
||||
return ''
|
||||
}, [payload.author, payload.type, t])
|
||||
|
||||
return (
|
||||
<div
|
||||
key={payload.id}
|
||||
className='mb-1 last-of-type:mb-0'
|
||||
className={cn('mb-1 last-of-type:mb-0', isShowLetterIndex && 'mr-6')}
|
||||
>
|
||||
<div className={cn(className)}>
|
||||
<div
|
||||
@ -69,16 +84,21 @@ const Tool: FC<Props> = ({
|
||||
/>
|
||||
<div className='ml-2 text-sm text-gray-900 flex-1 w-0 grow truncate'>{payload.label[language]}</div>
|
||||
</div>
|
||||
{hasAction && (
|
||||
<FoldIcon className={cn('w-4 h-4 text-text-quaternary shrink-0', isFold && 'text-text-tertiary')} />
|
||||
)}
|
||||
|
||||
<div className='flex items-center'>
|
||||
{isFlatView && (
|
||||
<div className='text-text-tertiary system-xs-regular'>{groupName}</div>
|
||||
)}
|
||||
{hasAction && (
|
||||
<FoldIcon className={cn('w-4 h-4 text-text-quaternary shrink-0', isFold && 'text-text-tertiary')} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasAction && isFold && (
|
||||
actions.map(action => (
|
||||
<ActonItem
|
||||
key={action.name}
|
||||
className={cn(isShowLetterIndex && 'mr-6')}
|
||||
provider={payload}
|
||||
payload={action}
|
||||
onSelect={onSelect}
|
||||
|
@ -28,7 +28,6 @@ const Blocks = ({
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
const isFlatView = viewType === ViewType.flat
|
||||
const isTreeView = viewType === ViewType.tree
|
||||
const isShowLetterIndex = isFlatView && tools.length > 10
|
||||
|
||||
/*
|
||||
@ -61,6 +60,16 @@ const Blocks = ({
|
||||
return result
|
||||
}, [withLetterAndGroupViewToolsData])
|
||||
|
||||
const listViewToolData = useMemo(() => {
|
||||
const result: ToolWithProvider[] = []
|
||||
Object.keys(withLetterAndGroupViewToolsData).forEach((letter) => {
|
||||
Object.keys(withLetterAndGroupViewToolsData[letter]).forEach((groupName) => {
|
||||
result.push(...withLetterAndGroupViewToolsData[letter][groupName])
|
||||
})
|
||||
})
|
||||
return result
|
||||
}, [withLetterAndGroupViewToolsData])
|
||||
|
||||
const toolRefs = useRef({})
|
||||
|
||||
return (
|
||||
@ -78,6 +87,9 @@ const Blocks = ({
|
||||
{!!tools.length && (
|
||||
isFlatView ? (
|
||||
<ToolListFlatView
|
||||
payload={listViewToolData}
|
||||
isShowLetterIndex={isShowLetterIndex}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
) : (
|
||||
<ToolListTreeView
|
||||
|
Loading…
Reference in New Issue
Block a user