123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- 'use client'
- import React, { useMemo, useState } from 'react'
- import { useRouter } from 'next/navigation'
- import { useTranslation } from 'react-i18next'
- import { useContext } from 'use-context-selector'
- import useSWR from 'swr'
- import { useDebounceFn } from 'ahooks'
- import Toast from '../../base/toast'
- import s from './style.module.css'
- import cn from '@/utils/classnames'
- import ExploreContext from '@/context/explore-context'
- import type { App } from '@/models/explore'
- import Category from '@/app/components/explore/category'
- import AppCard from '@/app/components/explore/app-card'
- 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 Input from '@/app/components/base/input'
- type AppsProps = {
- pageType?: PageType
- onSuccess?: () => void
- }
- export enum PageType {
- EXPLORE = 'explore',
- CREATE = 'create',
- }
- const Apps = ({
- pageType = PageType.EXPLORE,
- onSuccess,
- }: AppsProps) => {
- const { t } = useTranslation()
- const { isCurrentWorkspaceEditor } = useAppContext()
- const { push } = useRouter()
- const { hasEditPermission } = useContext(ExploreContext)
- const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' })
- const [keywords, setKeywords] = useState('')
- const [searchKeywords, setSearchKeywords] = useState('')
- const { run: handleSearch } = useDebounceFn(() => {
- setSearchKeywords(keywords)
- }, { wait: 500 })
- const handleKeywordsChange = (value: string) => {
- setKeywords(value)
- handleSearch()
- }
- const [currentType, setCurrentType] = useState<string>('')
- const [currCategory, setCurrCategory] = useTabSearchParams({
- defaultTab: allCategoriesEn,
- disableSearchParams: pageType !== PageType.EXPLORE,
- })
- const {
- data: { categories, allList },
- } = useSWR(
- ['/explore/apps'],
- () =>
- fetchAppList().then(({ categories, recommended_apps }) => ({
- categories,
- allList: recommended_apps.sort((a, b) => a.position - b.position),
- })),
- {
- fallbackData: {
- categories: [],
- allList: [],
- },
- },
- )
- 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 searchFilteredList = useMemo(() => {
- if (!searchKeywords || !filteredList || filteredList.length === 0)
- return filteredList
- const lowerCaseSearchKeywords = searchKeywords.toLowerCase()
- return filteredList.filter(item =>
- item.app && item.app.name && item.app.name.toLowerCase().includes(lowerCaseSearchKeywords),
- )
- }, [searchKeywords, filteredList])
- const [currApp, setCurrApp] = React.useState<App | null>(null)
- const [isShowCreateModal, setIsShowCreateModal] = React.useState(false)
- const onCreate: CreateAppModalProps['onConfirm'] = async ({
- name,
- icon_type,
- icon,
- icon_background,
- description,
- }) => {
- const { export_data } = await fetchAppDetail(
- currApp?.app.id as string,
- )
- try {
- const app = await importApp({
- data: export_data,
- name,
- icon_type,
- icon,
- icon_background,
- description,
- })
- setIsShowCreateModal(false)
- Toast.notify({
- type: 'success',
- message: t('app.newApp.appCreated'),
- })
- if (onSuccess)
- onSuccess()
- localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
- getRedirection(isCurrentWorkspaceEditor, app, push)
- }
- catch (e) {
- Toast.notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
- }
- }
- if (!categories || categories.length === 0) {
- return (
- <div className="flex h-full items-center">
- <Loading type="area" />
- </div>
- )
- }
- return (
- <div className={cn(
- 'flex flex-col',
- pageType === PageType.EXPLORE ? 'h-full border-l border-gray-200' : 'h-[calc(100%-56px)]',
- )}>
- {pageType === PageType.EXPLORE && (
- <div className='shrink-0 pt-6 px-12'>
- <div className={`mb-1 ${s.textGradient} text-xl font-semibold`}>{t('explore.apps.title')}</div>
- <div className='text-gray-500 text-sm'>{t('explore.apps.description')}</div>
- </div>
- )}
- <div className={cn(
- 'flex items-center justify-between mt-6',
- pageType === PageType.EXPLORE ? 'px-12' : 'px-8',
- )}>
- <>
- {pageType !== PageType.EXPLORE && (
- <>
- <AppTypeSelector value={currentType} onChange={setCurrentType}/>
- <div className='mx-2 w-[1px] h-3.5 bg-gray-200'/>
- </>
- )}
- <Category
- list={categories}
- value={currCategory}
- onChange={setCurrCategory}
- allCategoriesEn={allCategoriesEn}
- />
- </>
- <Input
- showLeftIcon
- showClearIcon
- wrapperClassName='w-[200px]'
- value={keywords}
- onChange={e => handleKeywordsChange(e.target.value)}
- onClear={() => handleKeywordsChange('')}
- />
- </div>
- <div className={cn(
- 'relative flex flex-1 pb-6 flex-col overflow-auto bg-gray-100 shrink-0 grow',
- pageType === PageType.EXPLORE ? 'mt-4' : 'mt-0 pt-2',
- )}>
- <nav
- className={cn(
- s.appList,
- 'grid content-start shrink-0',
- pageType === PageType.EXPLORE ? 'gap-4 px-6 sm:px-12' : 'gap-3 px-8 sm:!grid-cols-2 md:!grid-cols-3 lg:!grid-cols-4',
- )}>
- {searchFilteredList.map(app => (
- <AppCard
- key={app.app_id}
- isExplore={pageType === PageType.EXPLORE}
- app={app}
- canCreate={hasEditPermission}
- onCreate={() => {
- setCurrApp(app)
- setIsShowCreateModal(true)
- }}
- />
- ))}
- </nav>
- </div>
- {isShowCreateModal && (
- <CreateAppModal
- appIconType={currApp?.app.icon_type || 'emoji'}
- appIcon={currApp?.app.icon || ''}
- appIconBackground={currApp?.app.icon_background || ''}
- appIconUrl={currApp?.app.icon_url}
- appName={currApp?.app.name || ''}
- appDescription={currApp?.app.description || ''}
- show={isShowCreateModal}
- onConfirm={onCreate}
- onHide={() => setIsShowCreateModal(false)}
- />
- )}
- </div>
- )
- }
- export default React.memo(Apps)
|