'use client' import { useCallback, useEffect, useRef, useState } from 'react' import { useRouter, } from 'next/navigation' import useSWRInfinite from 'swr/infinite' import { useTranslation } from 'react-i18next' import { useDebounceFn } from 'ahooks' import { RiApps2Line, RiExchange2Line, RiFile4Line, RiMessage3Line, RiRobot3Line, } from '@remixicon/react' import AppCard from './AppCard' import NewAppCard from './NewAppCard' import useAppsQueryState from './hooks/useAppsQueryState' import type { AppListResponse } from '@/models/app' import { fetchAppList } from '@/service/apps' import { useAppContext } from '@/context/app-context' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { CheckModal } from '@/hooks/use-pay' import TabSliderNew from '@/app/components/base/tab-slider-new' import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import Input from '@/app/components/base/input' import { useStore as useTagStore } from '@/app/components/base/tag-management/store' import TagManagementModal from '@/app/components/base/tag-management' import TagFilter from '@/app/components/base/tag-management/filter' import { fetchDepts } from '@/service/common' import { TreeSelect as AntdTreeSelect } from 'antd' import { SimpleSelect } from '@/app/components/base/select' const getKey = ( pageIndex: number, previousPageData: AppListResponse, activeTab: string, isCreatedByMe: boolean, tags: string[], keywords: string, dept: string, authType: number, creator: string, ) => { if (!pageIndex || previousPageData.has_more) { const params: any = { url: 'apps', params: { page: pageIndex + 1, limit: 30, name: keywords, is_created_by_me: isCreatedByMe } } if (activeTab !== 'all') params.params.mode = activeTab else delete params.params.mode if (tags.length) params.params.tag_ids = tags if (dept) params.params.creator_dept = dept if (authType) params.params.auth_type = authType if (creator) params.params.creator = creator return params } return null } const Apps = () => { const { t } = useTranslation() const router = useRouter() const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext() const showTagManagementModal = useTagStore(s => s.showTagManagementModal) const [activeTab, setActiveTab] = useTabSearchParams({ defaultTab: 'all', }) const { query: { tagIDs = [], keywords = '', isCreatedByMe: queryIsCreatedByMe = false }, setQuery } = useAppsQueryState() const [isCreatedByMe, setIsCreatedByMe] = useState(queryIsCreatedByMe) const [tagFilterValue, setTagFilterValue] = useState(tagIDs) const [searchKeywords, setSearchKeywords] = useState(keywords) const setKeywords = useCallback((keywords: string) => { setQuery(prev => ({ ...prev, keywords })) }, [setQuery]) const setTagIDs = useCallback((tagIDs: string[]) => { setQuery(prev => ({ ...prev, tagIDs })) }, [setQuery]) const [dept, setDept] = useState() const [optionsDept, setOptionsDept] = useState([]) useEffect(() => { fetchDepts({ url: '/depts', }).then((res: any) => { setOptionsDept(res.data || []) }) }, []) const [authType, setAuthType] = useState('') const optionsAuthType = [ { name: '创建', value: '1' }, { name: '编辑', value: '2' }, { name: '授权编辑', value: '3' }, { name: '授权可见', value: '4' }, ] const [creator, setCreator] = useState('') const [searchCreator, setSearchCreator] = useState('') const { run: handleSearchCreator } = useDebounceFn(() => { setSearchCreator(creator) }, { wait: 500 }) const handleCreatorChange = (value: string) => { setCreator(value) handleSearchCreator() } const { data, isLoading, setSize, mutate } = useSWRInfinite( (pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, activeTab, isCreatedByMe, tagIDs, searchKeywords, dept, authType, searchCreator), fetchAppList, { revalidateFirstPage: true }, ) const anchorRef = useRef(null) const options = [ { value: 'all', 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.types.completion'), icon: }, { value: 'advanced-chat', text: t('app.types.advanced'), icon: }, { value: 'workflow', text: t('app.types.workflow'), icon: }, ] useEffect(() => { document.title = `${t('common.menus.apps')} - Dify` if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') { localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY) mutate() } }, [mutate, t]) useEffect(() => { if (isCurrentWorkspaceDatasetOperator) return router.replace('/datasets') }, [router, isCurrentWorkspaceDatasetOperator]) useEffect(() => { const hasMore = data?.at(-1)?.has_more ?? true let observer: IntersectionObserver | undefined if (anchorRef.current) { observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting && !isLoading && hasMore) setSize((size: number) => size + 1) }, { rootMargin: '100px' }) observer.observe(anchorRef.current) } return () => observer?.disconnect() }, [isLoading, setSize, anchorRef, mutate, data]) const { run: handleSearch } = useDebounceFn(() => { setSearchKeywords(keywords) }, { wait: 500 }) const handleKeywordsChange = (value: string) => { setKeywords(value) handleSearch() } const { run: handleTagsUpdate } = useDebounceFn(() => { setTagIDs(tagFilterValue) }, { wait: 500 }) const handleTagsChange = (value: string[]) => { setTagFilterValue(value) handleTagsUpdate() } const handleCreatedByMeChange = useCallback(() => { const newValue = !isCreatedByMe setIsCreatedByMe(newValue) setQuery(prev => ({ ...prev, isCreatedByMe: newValue })) }, [isCreatedByMe, setQuery]) return ( <>
handleCreatorChange(e.target.value)} onClear={() => handleCreatorChange('')} placeholder="请输入创建人" /> { setAuthType(i.value) }} items={optionsAuthType} allowSearch={false} placeholder="请选择权限类型" /> setDept(v || '')} treeData={optionsDept} fieldNames={{ label: 'dept_name', value: 'dept_id' }} /> {/* */} handleKeywordsChange(e.target.value)} onClear={() => handleKeywordsChange('')} />
{(data && data[0].total > 0) ?
{isCurrentWorkspaceEditor && } {data.map(({ data: apps }) => apps.map(app => ( )))}
:
{isCurrentWorkspaceEditor && }
}
{showTagManagementModal && ( )} ) } export default Apps function NoAppsFound() { const { t } = useTranslation() function renderDefaultCard() { const defaultCards = Array.from({ length: 36 }, (_, index) => (
)) return defaultCards } return ( <> {renderDefaultCard()}
{t('app.newApp.noAppsFound')}
) } export const GetAppAuth = (row: any) => { const { currentWorkspace, userProfile } = useAppContext() let isCreate = false let isEdit = false let isOperation = false if (row) { isCreate = currentWorkspace.role === 'owner' || row.created_by === userProfile.id isEdit = isCreate || currentWorkspace.role === 'admin' || (row.edit_auth === 2 && currentWorkspace.role === 'leader' && row.dept_id === userProfile.dept_id) isOperation = isEdit || (row.edit_auth === 2 && row.dept_id === userProfile.dept_id) } return { isCreate, isEdit, isOperation, } }