'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 (
)
}
export function CreateInstanceModal({ open, onOpenChange }: {
open: boolean
onOpenChange: (open: boolean) => void
}) {
return (
)
}