This commit is contained in:
2025-12-01 17:21:38 +08:00
parent 32fee2b8ab
commit fab8c13cb3
7511 changed files with 996300 additions and 0 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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)