'use client' import type { App } from '@/types/app' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxInputGroup, ComboboxItem, ComboboxItemText, ComboboxList, ComboboxTrigger, } from '@langgenius/dify-ui/combobox' import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog' import { toast } from '@langgenius/dify-ui/toast' import { keepPreviousData, useInfiniteQuery, useMutation } from '@tanstack/react-query' import { useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Input from '@/app/components/base/input' import { SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton' import { useRouter } from '@/next/navigation' import { consoleQuery } from '@/service/client' const SOURCE_APP_PAGE_SIZE = 20 const SOURCE_APP_PICKER_SKELETON_KEYS = ['first-source-app', 'second-source-app', 'third-source-app'] function sourceAppSearchText(app: App) { return `${app.name} ${app.id} ${app.mode}`.toLowerCase() } function SourceAppTrigger({ open, app }: { open: boolean app?: App }) { const { t } = useTranslation('deployments') return ( {app && ( )} {app?.name ?? t('createModal.appPickerPlaceholder')} ) } function SourceAppOption({ app }: { app: App }) { const { t } = useTranslation('deployments') const modeLabel = t(`appMode.${app.mode}`, { defaultValue: app.mode }) return ( {app.name} ( {app.id.slice(0, 8)} ) {modeLabel} ) } function SourceAppPickerSkeleton() { return (
{SOURCE_APP_PICKER_SKELETON_KEYS.map(key => ( ))}
) } function SourceAppPicker({ value, onChange }: { value?: App onChange: (app: App) => void }) { const { t } = useTranslation('deployments') const [isShow, setIsShow] = useState(false) const [searchText, setSearchText] = useState('') const { data, isLoading, isFetchingNextPage, fetchNextPage, hasNextPage, } = useInfiniteQuery({ ...consoleQuery.apps.list.infiniteOptions({ input: pageParam => ({ query: { page: Number(pageParam), limit: SOURCE_APP_PAGE_SIZE, name: searchText, }, }), getNextPageParam: lastPage => lastPage.has_more ? lastPage.page + 1 : undefined, initialPageParam: 1, placeholderData: keepPreviousData, }), }) const apps = data?.pages.flatMap(page => page.data) ?? [] return ( items={apps} open={isShow} inputValue={searchText} onOpenChange={setIsShow} onInputValueChange={setSearchText} onValueChange={(app) => { if (!app) return onChange(app) setIsShow(false) }} itemToStringLabel={app => app?.name ?? ''} itemToStringValue={app => app?.id ?? ''} filter={(app, query) => sourceAppSearchText(app).includes(query.toLowerCase())} disabled={false} >
{(isLoading || isFetchingNextPage) && apps.length === 0 && } {(app: App) => ( )} {!(isLoading || isFetchingNextPage) && ( {t('createModal.appSearchEmpty')} )} {hasNextPage && (
)}
) } function CreateInstanceForm({ onClose }: { onClose: () => void }) { const { t } = useTranslation('deployments') const router = useRouter() const createInstance = useMutation(consoleQuery.enterprise.appInstanceService.createAppInstance.mutationOptions()) const [sourceApp, setSourceApp] = useState() const canCreate = Boolean(sourceApp?.id && !createInstance.isPending) const handleCreate = async (form: HTMLFormElement) => { if (!canCreate || !sourceApp?.id) return const formData = new FormData(form) const name = String(formData.get('name') ?? '').trim() const description = String(formData.get('description') ?? '').trim() if (!name) return try { const result = await createInstance.mutateAsync({ body: { sourceAppId: sourceApp.id, name: name.trim(), description: description.trim() || undefined, }, }) if (!result.appInstanceId) throw new Error('Create app instance did not return an appInstanceId.') onClose() router.push(`/deployments/${result.appInstanceId}/overview`) } catch { toast.error(t('createModal.createFailed')) } } return (
{ event.preventDefault() void handleCreate(event.currentTarget) }} >
{t('createModal.title')} {t('createModal.description')}