'use client' import type { SnippetListItem } from '@/types/snippet' import { cn } from '@langgenius/dify-ui/cn' import { Input } from '@langgenius/dify-ui/input' import { useDebounce } from 'ahooks' import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useAppContext } from '@/context/app-context' import { TagFilter } from '@/features/tag-management/components/tag-filter' import useDocumentTitle from '@/hooks/use-document-title' import dynamic from '@/next/dynamic' import Link from '@/next/link' import { useInfiniteSnippetList } from '@/service/use-snippets' import CreatorsFilter from '../apps/creators-filter' import Empty from '../apps/empty' import { StudioListHeader } from '../apps/studio-list-header' import SnippetCard from './components/snippet-card' import SnippetCreateButton from './components/snippet-create-button' import { SNIPPET_LIST_SEARCH_DEBOUNCE_MS } from './constants' import { useSnippetsQueryState } from './hooks/use-snippets-query-state' const TagManagementModal = dynamic(() => import('@/features/tag-management/components/tag-management-modal').then(mod => mod.TagManagementModal), { ssr: false, }) const SNIPPET_CARD_SKELETON_KEYS = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth'] type SnippetCardSkeletonProps = { count: number } const SnippetCardSkeleton = ({ count }: SnippetCardSkeletonProps) => { return ( <> {SNIPPET_CARD_SKELETON_KEYS.slice(0, count).map(key => (
))} ) } const SnippetList = () => { const { t } = useTranslation() const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator, isLoadingCurrentWorkspace } = useAppContext() // eslint-disable-next-line react/use-state -- custom URL query hook, not React.useState const { query: { tagIDs, keywords, creatorIDs }, setKeywords, setTagIDs, setCreatorIDs, } = useSnippetsQueryState() const debouncedKeywords = useDebounce(keywords, { wait: SNIPPET_LIST_SEARCH_DEBOUNCE_MS }) const containerRef = useRef(null) const anchorRef = useRef(null) const [showTagManagementModal, setShowTagManagementModal] = useState(false) useDocumentTitle(t('tabs.snippets', { ns: 'workflow' })) const snippetListQuery = useMemo(() => ({ page: 1, limit: 30, keyword: debouncedKeywords, ...(tagIDs.length ? { tag_ids: tagIDs } : {}), ...(creatorIDs.length ? { creator_ids: creatorIDs } : {}), }), [creatorIDs, debouncedKeywords, tagIDs]) const { data, isLoading, isFetching, isFetchingNextPage, fetchNextPage, hasNextPage, error, refetch, } = useInfiniteSnippetList(snippetListQuery, { enabled: !isCurrentWorkspaceDatasetOperator, }) useEffect(() => { if (isCurrentWorkspaceDatasetOperator) return const hasMore = hasNextPage ?? true let observer: IntersectionObserver | undefined if (error) { if (observer) observer.disconnect() return } if (anchorRef.current && containerRef.current) { const containerHeight = containerRef.current.clientHeight const dynamicMargin = Math.max(100, Math.min(containerHeight * 0.2, 200)) observer = new IntersectionObserver((entries) => { if (entries[0]!.isIntersecting && !isLoading && !isFetchingNextPage && !error && hasMore) fetchNextPage() }, { root: containerRef.current, rootMargin: `${dynamicMargin}px`, threshold: 0.1, }) observer.observe(anchorRef.current) } return () => observer?.disconnect() }, [error, fetchNextPage, hasNextPage, isCurrentWorkspaceDatasetOperator, isFetchingNextPage, isLoading]) const pages = useMemo(() => data?.pages ?? [], [data?.pages]) const snippets = useMemo(() => pages.flatMap(({ data: pageSnippets }) => pageSnippets), [pages]) const hasAnySnippet = (pages[0]?.total ?? 0) > 0 const showSkeleton = isLoading || (isFetching && pages.length === 0) return (
{t('menus.apps', { ns: 'common' })} /

{t('tabs.snippets', { ns: 'workflow' })}

)} >
setShowTagManagementModal(true)} />
setKeywords(e.target.value)} placeholder={t('tabs.searchSnippets', { ns: 'workflow' })} /> {!!keywords && ( )}
{(isCurrentWorkspaceEditor || isLoadingCurrentWorkspace) && ( )}
{showSkeleton ? : hasAnySnippet ? snippets.map(snippet => ( setShowTagManagementModal(true)} onRefresh={refetch} onTagsChange={refetch} /> )) : } {isFetchingNextPage && ( )}
setShowTagManagementModal(false)} onTagsChange={refetch} />
) } export default SnippetList