diff --git a/web/app/components/app/type-selector/index.tsx b/web/app/components/app/type-selector/index.tsx new file mode 100644 index 0000000000..013a302994 --- /dev/null +++ b/web/app/components/app/type-selector/index.tsx @@ -0,0 +1,128 @@ +import { useTranslation } from 'react-i18next' +import React, { useState } from 'react' +import cn from 'classnames' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows' +import { Check, DotsGrid } from '@/app/components/base/icons/src/vender/line/general' +import { XCircle } from '@/app/components/base/icons/src/vender/solid/general' +import { ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication' +import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' +export type AppSelectorProps = { + value: string + onChange: (value: string) => void +} + +const AppTypeSelector = ({ value, onChange }: AppSelectorProps) => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + + return ( + +
+ setOpen(v => !v)} + className='block' + > +
+ {!value && ( + <> +
+ +
+
{t('app.typeSelector.all')}
+
+ +
+ + )} + {value === 'chatbot' && ( + <> +
+ +
+
{t('app.typeSelector.chatbot')}
+
{ + e.stopPropagation() + onChange('') + }}> + +
+ + )} + {value === 'agent' && ( + <> +
+ +
+
{t('app.typeSelector.agent')}
+
{ + e.stopPropagation() + onChange('') + }}> + +
+ + )} + {value === 'workflow' && ( + <> +
+ +
+
{t('app.typeSelector.workflow')}
+
{ + e.stopPropagation() + onChange('') + }}> + +
+ + )} +
+
+ +
+
{ + onChange('chatbot') + setOpen(false) + }}> + +
{t('app.typeSelector.chatbot')}
+ {value === 'chatbot' && } +
+
{ + onChange('agent') + setOpen(false) + }}> + +
{t('app.typeSelector.agent')}
+ {value === 'agent' && } +
+
{ + onChange('workflow') + setOpen(false) + }}> + +
{t('app.typeSelector.workflow')}
+ {value === 'workflow' && } +
+
+
+
+
+ ) +} + +export default React.memo(AppTypeSelector) diff --git a/web/app/components/base/tab-slider-new/index.tsx b/web/app/components/base/tab-slider-new/index.tsx index d39ff8bd4f..05b330212a 100644 --- a/web/app/components/base/tab-slider-new/index.tsx +++ b/web/app/components/base/tab-slider-new/index.tsx @@ -25,7 +25,7 @@ const TabSliderNew: FC = ({ key={option.value} onClick={() => onChange(option.value)} className={cn( - 'mr-1 px-3 py-[5px] h-[28px] flex items-center rounded-lg border-[0.5px] border-transparent text-gray-700 text-[13px] font-medium leading-[18px] cursor-pointer hover:bg-gray-200', + 'mr-1 px-3 py-[7px] h-[32px] flex items-center rounded-lg border-[0.5px] border-transparent text-gray-700 text-[13px] font-medium leading-[18px] cursor-pointer hover:bg-gray-200', value === option.value && 'bg-white border-gray-200 shadow-xs text-primary-600 hover:bg-white', )} > diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index ce652ea863..48b588194d 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -1,6 +1,6 @@ 'use client' -import React from 'react' +import React, { useMemo, useState } from 'react' import cn from 'classnames' import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' @@ -16,19 +16,12 @@ import { fetchAppDetail, fetchAppList } from '@/service/explore' import { importApp } from '@/service/apps' import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import CreateAppModal from '@/app/components/explore/create-app-modal' +import AppTypeSelector from '@/app/components/app/type-selector' import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' import Loading from '@/app/components/base/loading' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { useAppContext } from '@/context/app-context' import { getRedirection } from '@/utils/app-redirection' -import TabSliderNew from '@/app/components/base/tab-slider-new' -import { DotsGrid } from '@/app/components/base/icons/src/vender/line/general' -import { Route } from '@/app/components/base/icons/src/vender/line/mapsAndTravel' -import { - AiText, - ChatBot, - CuteRobot, -} from '@/app/components/base/icons/src/vender/line/communication' type AppsProps = { pageType?: PageType @@ -48,17 +41,11 @@ const Apps = ({ const { hasEditPermission } = useContext(ExploreContext) const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' }) + const [currentType, setCurrentType] = useState('') const [currCategory, setCurrCategory] = useTabSearchParams({ defaultTab: allCategoriesEn, disableSearchParams: pageType !== PageType.EXPLORE, }) - const options = [ - { value: allCategoriesEn, text: t('app.types.all'), icon: }, - { value: 'chat', text: t('app.types.chatbot'), icon: }, - { value: 'agent-chat', text: t('app.types.agent'), icon: }, - { value: 'completion', text: t('app.newApp.completeApp'), icon: }, - { value: 'workflow', text: t('app.types.workflow'), icon: }, - ] const { data: { categories, allList }, @@ -77,17 +64,28 @@ const Apps = ({ }, ) - const currList - = currCategory === allCategoriesEn - ? allList - : allList.filter((item) => { - if (pageType === PageType.EXPLORE) - return item.category === currCategory - else if (currCategory === 'chat') - return item.app.mode === 'chat' || item.app.mode === 'advanced-chat' - else - return item.app.mode === currCategory - }) + const filteredList = useMemo(() => { + if (currCategory === allCategoriesEn) { + if (!currentType) + return allList + else if (currentType === 'chatbot') + return allList.filter(item => (item.app.mode === 'chat' || item.app.mode === 'advanced-chat')) + else if (currentType === 'agent') + return allList.filter(item => (item.app.mode === 'agent-chat')) + else + return allList.filter(item => (item.app.mode === 'workflow')) + } + else { + if (!currentType) + return allList.filter(item => item.category === currCategory) + else if (currentType === 'chatbot') + return allList.filter(item => (item.app.mode === 'chat' || item.app.mode === 'advanced-chat') && item.category === currCategory) + else if (currentType === 'agent') + return allList.filter(item => (item.app.mode === 'agent-chat') && item.category === currCategory) + else + return allList.filter(item => (item.app.mode === 'workflow') && item.category === currCategory) + } + }, [currentType, currCategory, allCategoriesEn, allList]) const [currApp, setCurrApp] = React.useState(null) const [isShowCreateModal, setIsShowCreateModal] = React.useState(false) @@ -140,23 +138,23 @@ const Apps = ({
{t('explore.apps.description')}
)} - {pageType === PageType.EXPLORE && ( +
+ {pageType !== PageType.EXPLORE && ( + <> + +
+ + )} - )} - {pageType !== PageType.EXPLORE && ( - - )} +
- {currList.map(app => ( + {filteredList.map(app => ( = ({ const isAllCategories = !list.includes(value) const itemClassName = (isSelected: boolean) => cn( - 'px-3 py-[5px] h-[28px] rounded-lg border-[0.5px] border-transparent text-gray-700 font-medium leading-[18px] cursor-pointer hover:bg-gray-200', + 'flex items-center px-3 py-[7px] h-[32px] rounded-lg border-[0.5px] border-transparent text-gray-700 font-medium leading-[18px] cursor-pointer hover:bg-gray-200', isSelected && 'bg-white border-gray-200 shadow-xs text-primary-600 hover:bg-white', ) @@ -40,6 +41,7 @@ const Category: FC = ({ className={itemClassName(isAllCategories)} onClick={() => onChange(allCategoriesEn)} > + {t('explore.apps.allCategories')}
{list.map(name => ( diff --git a/web/i18n/en-US/app.ts b/web/i18n/en-US/app.ts index 3689095243..c180443a6d 100644 --- a/web/i18n/en-US/app.ts +++ b/web/i18n/en-US/app.ts @@ -78,6 +78,13 @@ const translation = { switchLabel: 'The app copy to be created', removeOriginal: 'Delete the original app', switchStart: 'Start swtich', + typeSelector: { + all: 'ALL Types', + chatbot: 'Chatbot', + agent: 'Agent', + workflow: 'Workflow', + completion: 'Completion', + }, } export default translation diff --git a/web/i18n/en-US/explore.ts b/web/i18n/en-US/explore.ts index 330ae84b3a..c996f51820 100644 --- a/web/i18n/en-US/explore.ts +++ b/web/i18n/en-US/explore.ts @@ -18,7 +18,7 @@ const translation = { apps: { title: 'Explore Apps by Dify', description: 'Use these template apps instantly or customize your own apps based on the templates.', - allCategories: 'All Categories', + allCategories: 'Recommended', }, appCard: { addToWorkspace: 'Add to Workspace', diff --git a/web/i18n/zh-Hans/app.ts b/web/i18n/zh-Hans/app.ts index 803b666d88..dd603951f2 100644 --- a/web/i18n/zh-Hans/app.ts +++ b/web/i18n/zh-Hans/app.ts @@ -77,6 +77,13 @@ const translation = { switchLabel: '新应用创建为', removeOriginal: '删除原应用', switchStart: '开始迁移', + typeSelector: { + all: '所有类型', + chatbot: '聊天助手', + agent: 'Agent', + workflow: '工作流', + completion: '文本生成', + }, } export default translation diff --git a/web/i18n/zh-Hans/explore.ts b/web/i18n/zh-Hans/explore.ts index 84a78d6ac0..d4f4074205 100644 --- a/web/i18n/zh-Hans/explore.ts +++ b/web/i18n/zh-Hans/explore.ts @@ -18,7 +18,7 @@ const translation = { apps: { title: '探索 Dify 的应用', description: '使用这些模板应用程序,或根据模板自定义您自己的应用程序。', - allCategories: '所有类别', + allCategories: '推荐', }, appCard: { addToWorkspace: '添加到工作区',