'use client' import type { App, AppModeEnum } from '@/types/app' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog' import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQuery } from '@tanstack/react-query' import { useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { AppTypeIcon } from '@/app/components/app/type-selector' import AppIcon from '@/app/components/base/app-icon' import Input from '@/app/components/base/input' import { useRouter } from '@/next/navigation' import { consoleQuery } from '@/service/client' import { useDeploymentsStore } from '../store' const MAX_STUDIO_SOURCE_APPS = 100 type StudioSourceApp = { id: string name: string mode: string iconType?: App['icon_type'] icon?: string iconBackground?: string iconUrl?: string | null description?: string } function toStudioSourceAppInfo(app: App): StudioSourceApp { return { id: app.id, name: app.name, mode: app.mode || 'workflow', iconType: app.icon_type, icon: app.icon, iconBackground: app.icon_background ?? undefined, iconUrl: app.icon_url, description: app.description || undefined, } } type AppPickerProps = { apps: StudioSourceApp[] isLoading: boolean value: string onChange: (appId: string) => void } function AppPicker({ apps, isLoading, value, onChange }: AppPickerProps) { const { t } = useTranslation('deployments') const [open, setOpen] = useState(false) const [keywords, setKeywords] = useState('') const triggerRef = useRef(null) const [triggerWidth, setTriggerWidth] = useState(undefined) const q = keywords.trim().toLowerCase() const filtered = q ? apps.filter(a => a.name.toLowerCase().includes(q) || a.mode.toLowerCase().includes(q)) : apps const handleOpenChange = (next: boolean) => { if (next && triggerRef.current) setTriggerWidth(triggerRef.current.offsetWidth) if (!next) setKeywords('') setOpen(next) } if (isLoading) { return (
{t('createModal.loadingApps')}
) } const selected = apps.find(a => a.id === value) if (apps.length === 0) { return (
{t('createModal.noApps')}
) } return ( )} > {selected ? (
{selected.name} {selected.mode}
) : ( {t('createModal.appPickerPlaceholder')} )}
setKeywords(e.target.value)} onClear={() => setKeywords('')} autoFocus />
{filtered.length === 0 ? (
{t('createModal.appSearchEmpty')}
) : filtered.map((app) => { const isSelected = app.id === value return ( ) })}
) } function CreateInstanceForm({ onClose }: { onClose: () => void }) { const { t } = useTranslation('deployments') const router = useRouter() const createInstance = useMutation(consoleQuery.enterprise.appDeploy.createAppInstance.mutationOptions()) const { data: appList, isLoading } = useQuery(consoleQuery.apps.list.queryOptions({ input: { query: { page: 1, limit: MAX_STUDIO_SOURCE_APPS, name: '', }, }, })) const apps = (appList?.data ?? []).map(toStudioSourceAppInfo) const [appId, setAppId] = useState('') const [name, setName] = useState('') const [description, setDescription] = useState('') const selectedApp = apps.find(a => a.id === appId) const canCreate = Boolean(appId && name.trim() && !createInstance.isPending) const handleCreate = async () => { if (!canCreate) return try { const result = await createInstance.mutateAsync({ body: { sourceAppId: appId, 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 (
{t('createModal.title')} {t('createModal.description')}
setName(e.target.value)} className="flex h-8 items-center rounded-lg border-[0.5px] border-components-input-border-active bg-components-input-bg-normal px-3 text-[13px] font-medium text-text-secondary outline-hidden placeholder:text-text-quaternary" />