dify
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
'use client'
|
||||
import type { Dispatch, SetStateAction } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import type { OnSelectBlock } from '@/app/components/workflow/types'
|
||||
import type { ViewType } from '@/app/components/workflow/block-selector/view-type-select'
|
||||
import { RiMoreLine } from '@remixicon/react'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Link from 'next/link'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
import { useRAGRecommendedPlugins } from '@/service/use-tools'
|
||||
import List from './list'
|
||||
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
|
||||
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/arrows'
|
||||
|
||||
type RAGToolRecommendationsProps = {
|
||||
viewType: ViewType
|
||||
onSelect: OnSelectBlock
|
||||
onTagsChange: Dispatch<SetStateAction<string[]>>
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'workflow_rag_recommendations_collapsed'
|
||||
|
||||
const RAGToolRecommendations = ({
|
||||
viewType,
|
||||
onSelect,
|
||||
onTagsChange,
|
||||
}: RAGToolRecommendationsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [isCollapsed, setIsCollapsed] = useState<boolean>(() => {
|
||||
if (typeof window === 'undefined')
|
||||
return false
|
||||
const stored = window.localStorage.getItem(STORAGE_KEY)
|
||||
return stored === 'true'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined')
|
||||
return
|
||||
const stored = window.localStorage.getItem(STORAGE_KEY)
|
||||
if (stored !== null)
|
||||
setIsCollapsed(stored === 'true')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined')
|
||||
return
|
||||
window.localStorage.setItem(STORAGE_KEY, String(isCollapsed))
|
||||
}, [isCollapsed])
|
||||
|
||||
const {
|
||||
data: ragRecommendedPlugins,
|
||||
isLoading: isLoadingRAGRecommendedPlugins,
|
||||
isFetching: isFetchingRAGRecommendedPlugins,
|
||||
} = useRAGRecommendedPlugins()
|
||||
|
||||
const recommendedPlugins = useMemo(() => {
|
||||
if (ragRecommendedPlugins)
|
||||
return ragRecommendedPlugins.installed_recommended_plugins
|
||||
return []
|
||||
}, [ragRecommendedPlugins])
|
||||
|
||||
const unInstalledPlugins = useMemo(() => {
|
||||
if (ragRecommendedPlugins)
|
||||
return (ragRecommendedPlugins.uninstalled_recommended_plugins).map(getFormattedPlugin)
|
||||
return []
|
||||
}, [ragRecommendedPlugins])
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
onTagsChange((prev) => {
|
||||
if (prev.includes('rag'))
|
||||
return prev
|
||||
return [...prev, 'rag']
|
||||
})
|
||||
}, [onTagsChange])
|
||||
|
||||
return (
|
||||
<div className='flex flex-col p-1'>
|
||||
<button
|
||||
type='button'
|
||||
className='flex w-full items-center rounded-md px-3 pb-0.5 pt-1 text-left text-text-tertiary'
|
||||
onClick={() => setIsCollapsed(prev => !prev)}
|
||||
>
|
||||
<span className='system-xs-medium text-text-tertiary'>{t('pipeline.ragToolSuggestions.title')}</span>
|
||||
<ArrowDownRoundFill className={`ml-1 h-4 w-4 text-text-tertiary transition-transform ${isCollapsed ? '-rotate-90' : 'rotate-0'}`} />
|
||||
</button>
|
||||
{!isCollapsed && (
|
||||
<>
|
||||
{/* For first time loading, show loading */}
|
||||
{isLoadingRAGRecommendedPlugins && (
|
||||
<div className='py-2'>
|
||||
<Loading type='app' />
|
||||
</div>
|
||||
)}
|
||||
{!isFetchingRAGRecommendedPlugins && recommendedPlugins.length === 0 && unInstalledPlugins.length === 0 && (
|
||||
<p className='system-xs-regular px-3 py-1 text-text-tertiary'>
|
||||
<Trans
|
||||
i18nKey='pipeline.ragToolSuggestions.noRecommendationPlugins'
|
||||
components={{
|
||||
CustomLink: (
|
||||
<Link
|
||||
className='text-text-accent'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
href={getMarketplaceUrl('', { tags: 'rag' })}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
{(recommendedPlugins.length > 0 || unInstalledPlugins.length > 0) && (
|
||||
<>
|
||||
<List
|
||||
tools={recommendedPlugins}
|
||||
unInstalledPlugins={unInstalledPlugins}
|
||||
onSelect={onSelect}
|
||||
viewType={viewType}
|
||||
/>
|
||||
<div
|
||||
className='flex cursor-pointer items-center gap-x-2 py-1 pl-3 pr-2'
|
||||
onClick={loadMore}
|
||||
>
|
||||
<div className='px-1'>
|
||||
<RiMoreLine className='size-4 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
{t('common.operation.more')}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(RAGToolRecommendations)
|
||||
@@ -0,0 +1,104 @@
|
||||
import { useCallback, useMemo, useRef } from 'react'
|
||||
import type { BlockEnum, ToolWithProvider } from '../../types'
|
||||
import type { ToolDefaultValue } from '../types'
|
||||
import { ViewType } from '../view-type-select'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { groupItems } from '../index-bar'
|
||||
import cn from '@/utils/classnames'
|
||||
import ToolListTreeView from '../tool/tool-list-tree-view/list'
|
||||
import ToolListFlatView from '../tool/tool-list-flat-view/list'
|
||||
import UninstalledItem from './uninstalled-item'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import type { OnSelectBlock } from '@/app/components/workflow/types'
|
||||
|
||||
type ListProps = {
|
||||
onSelect: OnSelectBlock
|
||||
tools: ToolWithProvider[]
|
||||
viewType: ViewType
|
||||
unInstalledPlugins: Plugin[]
|
||||
className?: string
|
||||
}
|
||||
|
||||
const List = ({
|
||||
onSelect,
|
||||
tools,
|
||||
viewType,
|
||||
unInstalledPlugins,
|
||||
className,
|
||||
}: ListProps) => {
|
||||
const language = useGetLanguage()
|
||||
const isFlatView = viewType === ViewType.flat
|
||||
|
||||
const { letters, groups: withLetterAndGroupViewToolsData } = groupItems(tools, tool => tool.label[language][0])
|
||||
const treeViewToolsData = useMemo(() => {
|
||||
const result: Record<string, ToolWithProvider[]> = {}
|
||||
Object.keys(withLetterAndGroupViewToolsData).forEach((letter) => {
|
||||
Object.keys(withLetterAndGroupViewToolsData[letter]).forEach((groupName) => {
|
||||
if (!result[groupName])
|
||||
result[groupName] = []
|
||||
result[groupName].push(...withLetterAndGroupViewToolsData[letter][groupName])
|
||||
})
|
||||
})
|
||||
return result
|
||||
}, [withLetterAndGroupViewToolsData])
|
||||
|
||||
const listViewToolData = useMemo(() => {
|
||||
const result: ToolWithProvider[] = []
|
||||
letters.forEach((letter) => {
|
||||
Object.keys(withLetterAndGroupViewToolsData[letter]).forEach((groupName) => {
|
||||
result.push(...withLetterAndGroupViewToolsData[letter][groupName].map((item) => {
|
||||
return {
|
||||
...item,
|
||||
letter,
|
||||
}
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
return result
|
||||
}, [withLetterAndGroupViewToolsData, letters])
|
||||
|
||||
const toolRefs = useRef({})
|
||||
|
||||
const handleSelect = useCallback((type: BlockEnum, tool: ToolDefaultValue) => {
|
||||
onSelect(type, tool)
|
||||
}, [onSelect])
|
||||
|
||||
return (
|
||||
<div className={cn('max-w-[100%] p-1', className)}>
|
||||
{!!tools.length && (
|
||||
isFlatView ? (
|
||||
<ToolListFlatView
|
||||
toolRefs={toolRefs}
|
||||
letters={letters}
|
||||
payload={listViewToolData}
|
||||
isShowLetterIndex={false}
|
||||
hasSearchText={false}
|
||||
onSelect={handleSelect}
|
||||
canNotSelectMultiple
|
||||
indexBar={null}
|
||||
/>
|
||||
) : (
|
||||
<ToolListTreeView
|
||||
payload={treeViewToolsData}
|
||||
hasSearchText={false}
|
||||
onSelect={handleSelect}
|
||||
canNotSelectMultiple
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{
|
||||
unInstalledPlugins.map((item) => {
|
||||
return (
|
||||
<UninstalledItem
|
||||
key={item.plugin_id}
|
||||
payload={item}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default List
|
||||
@@ -0,0 +1,63 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
|
||||
import I18n from '@/context/i18n'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { BlockEnum } from '../../types'
|
||||
import BlockIcon from '../../block-icon'
|
||||
|
||||
type UninstalledItemProps = {
|
||||
payload: Plugin
|
||||
}
|
||||
|
||||
const UninstalledItem = ({
|
||||
payload,
|
||||
}: UninstalledItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
|
||||
const getLocalizedText = (obj: Record<string, string> | undefined) =>
|
||||
obj?.[locale] || obj?.['en-US'] || obj?.en_US || ''
|
||||
const [isShowInstallModal, {
|
||||
setTrue: showInstallModal,
|
||||
setFalse: hideInstallModal,
|
||||
}] = useBoolean(false)
|
||||
|
||||
return (
|
||||
<div className='flex h-8 items-center rounded-lg pl-3 pr-2 hover:bg-state-base-hover'>
|
||||
<BlockIcon
|
||||
className='shrink-0'
|
||||
type={BlockEnum.Tool}
|
||||
toolIcon={payload.icon}
|
||||
/>
|
||||
<div className='ml-2 flex w-0 grow items-center'>
|
||||
<div className='flex w-0 grow items-center gap-x-2'>
|
||||
<span className='system-sm-regular truncate text-text-primary'>
|
||||
{getLocalizedText(payload.label)}
|
||||
</span>
|
||||
<span className='system-xs-regular text-text-quaternary'>
|
||||
{payload.org}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className='system-xs-medium cursor-pointer pl-1.5 text-components-button-secondary-accent-text'
|
||||
onClick={showInstallModal}
|
||||
>
|
||||
{t('plugin.installAction')}
|
||||
</div>
|
||||
{isShowInstallModal && (
|
||||
<InstallFromMarketplace
|
||||
uniqueIdentifier={payload.latest_package_identifier}
|
||||
manifest={payload}
|
||||
onSuccess={hideInstallModal}
|
||||
onClose={hideInstallModal}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(UninstalledItem)
|
||||
Reference in New Issue
Block a user