'use client' import type { SourceAppPickerValue } from '../state' import type { App } from '@/types/app' import { cn } from '@langgenius/dify-ui/cn' import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxInputGroup, ComboboxItem, ComboboxItemText, ComboboxList, ComboboxTrigger, } from '@langgenius/dify-ui/combobox' import { useAtomValue, useSetAtom } from 'jotai' import { useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import { SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton' import { useInfiniteScroll } from '@/features/deployments/shared/hooks/use-infinite-scroll' import { TitleTooltip } from '../../shared/components/title-tooltip' import { createReleaseSourceAppsAtom, createReleaseSourceAppSearchTextAtom, createReleaseSourceAppsErrorAtom, createReleaseSourceAppsFetchNextPageAtom, createReleaseSourceAppsHasNextPageAtom, createReleaseSourceAppsIsFetchingAtom, createReleaseSourceAppsIsFetchingNextPageAtom, createReleaseSourceAppsIsLoadingAtom, } from '../state' const SOURCE_APP_PICKER_SKELETON_KEYS = [ 'first-source-app', 'second-source-app', 'third-source-app', ] function sourceAppSearchText(app: App) { return `${app.name} ${app.id}`.toLowerCase() } function SourceAppTrigger({ app }: { app?: SourceAppPickerValue }) { const { t } = useTranslation('deployments') return ( {app && ( )} {app?.name ?? t(($) => $['createModal.appPickerPlaceholder'])} ) } function SourceAppOption({ app }: { app: App }) { return ( {app.name} ({app.id.slice(0, 8)}) ) } function SourceAppPickerSkeleton() { return (
{SOURCE_APP_PICKER_SKELETON_KEYS.map((key) => ( ))}
) } export function SourceAppPicker({ value, onChange, disabled = false, }: { value?: SourceAppPickerValue onChange: (app: App) => void disabled?: boolean }) { const { t } = useTranslation('deployments') const [isShow, setIsShow] = useState(false) const searchText = useAtomValue(createReleaseSourceAppSearchTextAtom) const setSearchText = useSetAtom(createReleaseSourceAppSearchTextAtom) const apps = useAtomValue(createReleaseSourceAppsAtom) const sourceAppsError = useAtomValue(createReleaseSourceAppsErrorAtom) const sourceAppsFetchNextPage = useAtomValue(createReleaseSourceAppsFetchNextPageAtom) const sourceAppsHasNextPage = useAtomValue(createReleaseSourceAppsHasNextPageAtom) const sourceAppsIsFetching = useAtomValue(createReleaseSourceAppsIsFetchingAtom) const sourceAppsIsFetchingNextPage = useAtomValue(createReleaseSourceAppsIsFetchingNextPageAtom) const sourceAppsIsLoading = useAtomValue(createReleaseSourceAppsIsLoadingAtom) const { rootRef, sentinelRef } = useInfiniteScroll( { error: sourceAppsError, fetchNextPage: sourceAppsFetchNextPage, hasNextPage: sourceAppsHasNextPage, isFetching: sourceAppsIsFetching, isFetchingNextPage: sourceAppsIsFetchingNextPage, isLoading: sourceAppsIsLoading, }, { enabled: isShow && !disabled, rootMargin: '0px 0px 160px 0px', threshold: 0.1, }, ) return ( items={apps} open={!disabled && isShow} inputValue={searchText} onOpenChange={(open) => { setIsShow(disabled ? false : open) }} onInputValueChange={(value) => { if (!disabled) setSearchText(value) }} onValueChange={(app) => { if (disabled) return if (!app) return onChange(app) setIsShow(false) }} itemToStringLabel={(app) => { if (!app) return '' return app.name }} itemToStringValue={(app) => { if (!app) return '' return app.id }} filter={(app, query) => sourceAppSearchText(app).includes(query.toLowerCase())} disabled={disabled} > $['versions.sourceAppOption'])} icon={false} className="block h-auto w-full border-0 bg-transparent p-0 text-left hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 data-open:bg-transparent" >
{(sourceAppsIsLoading || sourceAppsIsFetchingNextPage) && apps.length === 0 && ( )} {(app: App) => } {!(sourceAppsIsLoading || sourceAppsIsFetchingNextPage) && ( {t(($) => $['createModal.appSearchEmpty'])} )} {sourceAppsIsFetchingNextPage && apps.length > 0 && (
{t(($) => $['createModal.loadingApps'])}
)} {sourceAppsHasNextPage &&
) }