'use client' import type { AppPartial } from '@dify/contracts/api/console/apps/types.gen' import type { ComboboxPositionerProps } from '@langgenius/dify-ui/combobox' import type { ReactNode } from 'react' import { zIconType } from '@dify/contracts/api/console/apps/zod.gen' import { Button } from '@langgenius/dify-ui/button' import { Combobox, ComboboxEmpty, ComboboxInput, ComboboxInputGroup, ComboboxItem, ComboboxItemText, ComboboxList, ComboboxPopup, ComboboxPortal, ComboboxPositioner, ComboboxStatus, ComboboxTrigger, } from '@langgenius/dify-ui/combobox' import { IconButton } from '@langgenius/dify-ui/icon-button' import { ScrollArea, ScrollAreaContent, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport, } from '@langgenius/dify-ui/scroll-area' import { useCallback, useRef } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import { AppModeEnum } from '@/types/app' type AppPickerProps = Pick & { scope?: string disabled: boolean trigger: ReactNode offset?: number isShow: boolean onShowChange: (isShow: boolean) => void onSelect: (app: AppPartial) => void apps: AppPartial[] isLoading: boolean hasMore: boolean onLoadMore: () => void searchText: string onSearchChange: (text: string) => void } function getAppTypeLabel(app: AppPartial) { switch (app.mode) { case AppModeEnum.ADVANCED_CHAT: return 'chatflow' case AppModeEnum.AGENT_CHAT: return 'agent' case AppModeEnum.CHAT: return 'chat' case AppModeEnum.COMPLETION: return 'completion' case AppModeEnum.WORKFLOW: return 'workflow' default: return app.mode } } function getAppSearchText(app: AppPartial) { return `${app.name} ${app.id} ${getAppTypeLabel(app)}` } function AppPickerOption({ app }: { app: AppPartial }) { const appIconType = zIconType.safeParse(app.icon_type).data ?? null return ( {app.name} ({app.id.slice(0, 8)}) {getAppTypeLabel(app)} ) } export function AppPicker({ disabled, trigger, placement = 'right-start', offset = 0, isShow, onShowChange, onSelect, apps, isLoading, hasMore, onLoadMore, searchText, onSearchChange, }: AppPickerProps) { const { t } = useTranslation() const inputRef = useRef(null) const handleValueChange = useCallback( (app: AppPartial | null) => { if (!app) return onSelect(app) onShowChange(false) }, [onSelect, onShowChange], ) const handleClearSearch = () => { onSearchChange('') inputRef.current?.focus() } return ( items={apps} open={isShow} inputValue={searchText} onOpenChange={onShowChange} onInputValueChange={onSearchChange} onValueChange={handleValueChange} itemToStringLabel={(app) => app?.name ?? ''} itemToStringValue={(app) => app?.id ?? ''} filter={(app, query) => getAppSearchText(app).toLowerCase().includes(query.toLowerCase())} disabled={disabled} > $['appSelector.label'], { ns: 'app' })} icon={false} className="block h-auto w-full border-0 bg-transparent p-0 text-left hover:bg-transparent focus-visible:bg-transparent data-popup-open:bg-transparent" > {trigger} $['appSelector.label'], { ns: 'app' })} className="border-0 bg-transparent p-0 shadow-none backdrop-blur-none" >
$['appSelector.label'], { ns: 'app' })} style={{ overflowX: 'hidden' }} > {isLoading ? t(($) => $.loading, { ns: 'common' }) : null} className="max-h-none overflow-visible p-0"> {(app) => } {!isLoading ? t(($) => $.noData, { ns: 'common' }) : null} {hasMore && (
)}
) }