'use client' import type { Placement } from '@langgenius/dify-ui/popover' import type { App } from '@/types/app' import { Popover, PopoverContent, PopoverTrigger, } from '@langgenius/dify-ui/popover' import { keepPreviousData, useInfiniteQuery } from '@tanstack/react-query' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import AppInputsPanel from '@/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel' import { AppPicker } from '@/app/components/plugins/plugin-detail-panel/app-selector/app-picker' import { AppTrigger } from '@/app/components/plugins/plugin-detail-panel/app-selector/app-trigger' import { consoleQuery } from '@/service/client' import { useAppDetail } from '@/service/use-apps' const PAGE_SIZE = 20 export type AppSelectorValue = { app_id: string inputs: Record files?: unknown[] } type AppSelectorProps = { value?: AppSelectorValue scope?: string disabled?: boolean placement?: Placement offset?: number onSelect: (app: AppSelectorValue) => void } export function AppSelector({ value, disabled, placement = 'bottom', offset = 4, onSelect, }: AppSelectorProps) { const { t } = useTranslation() const [isShow, setIsShow] = useState(false) const [isShowChooseApp, setIsShowChooseApp] = useState(false) const [searchText, setSearchText] = useState('') const appListQuery = useMemo(() => ({ page: 1, limit: PAGE_SIZE, name: searchText, }), [searchText]) const { data, isLoading, isFetchingNextPage, fetchNextPage, hasNextPage, } = useInfiniteQuery({ ...consoleQuery.apps.list.infiniteOptions({ input: pageParam => ({ query: { ...appListQuery, page: Number(pageParam), }, }), getNextPageParam: lastPage => lastPage.has_more ? lastPage.page + 1 : undefined, initialPageParam: 1, placeholderData: keepPreviousData, }), }) const displayedApps = useMemo(() => { return data?.pages.flatMap(({ data: apps }) => apps) ?? [] }, [data?.pages]) const { data: selectedAppDetail } = useAppDetail(value?.app_id || '') const currentAppInfo = useMemo(() => { if (!value?.app_id) return undefined return selectedAppDetail || displayedApps.find(app => app.id === value.app_id) }, [displayedApps, selectedAppDetail, value?.app_id]) const hasMore = hasNextPage ?? true const handleSelectApp = useCallback((app: App) => { const shouldClearValue = app.id !== value?.app_id onSelect({ app_id: app.id, inputs: shouldClearValue ? {} : value?.inputs || {}, files: shouldClearValue ? [] : value?.files || [], }) }, [onSelect, value?.app_id, value?.files, value?.inputs]) const handleFormChange = useCallback((inputs: Record) => { const newFiles = inputs['#image#'] const nextInputs = { ...inputs } delete nextInputs['#image#'] onSelect({ app_id: value?.app_id || '', inputs: nextInputs, files: newFiles ? [newFiles] : value?.files || [], }) }, [onSelect, value?.app_id, value?.files]) const formattedValue = useMemo(() => ({ app_id: value?.app_id || '', inputs: { ...value?.inputs, ...(value?.files?.length ? { '#image#': value.files[0] } : {}), }, }), [value]) return ( } >
{t('appSelector.label', { ns: 'app' })}
)} isShow={isShowChooseApp} onShowChange={setIsShowChooseApp} disabled={false} onSelect={handleSelectApp} apps={displayedApps} isLoading={isLoading || isFetchingNextPage} hasMore={hasMore} onLoadMore={() => { void fetchNextPage() }} searchText={searchText} onSearchChange={setSearchText} />
{currentAppInfo && ( )}
) }