'use client'
import type {
GuideMethod,
WorkflowSourceApp,
} from '@/features/deployments/create-guide/state/types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Input } from '@langgenius/dify-ui/input'
import { RadioGroup, RadioItem } from '@langgenius/dify-ui/radio'
import { useAtomValue, useSetAtom } from 'jotai'
import { useTranslation } from 'react-i18next'
import Uploader from '@/app/components/app/create-from-dsl-modal/uploader'
import AppIcon from '@/app/components/base/app-icon'
import { SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton'
import {
dslFileAtom,
effectiveMethodAtom,
sourceSearchTextAtom,
} from '@/features/deployments/create-guide/state/primitives'
import { unsupportedDslNodesAtom } from '@/features/deployments/create-guide/state/queries'
import {
dslReadErrorAtom,
dslUnsupportedModeAtom,
effectiveSelectedAppAtom,
isReadingDslAtom,
sourceAppsAtom,
sourceAppsErrorAtom,
sourceAppsFetchNextPageAtom,
sourceAppsHasNextPageAtom,
sourceAppsIsFetchingAtom,
sourceAppsIsFetchingNextPageAtom,
sourceAppsIsLoadingAtom,
sourceAppsIsPlaceholderDataAtom,
} from '@/features/deployments/create-guide/state/source'
import {
continueFromSourceAtom,
selectDslFileAtom,
selectMethodAtom,
selectSourceAppAtom,
setSourceSearchTextAtom,
sourceCanGoNextAtom,
} from '@/features/deployments/create-guide/state/workflow'
import { DeploymentStateMessage } from '@/features/deployments/shared/components/empty-state'
import { TitleTooltip } from '@/features/deployments/shared/components/title-tooltip'
import { UnsupportedDslNodesAlert } from '@/features/deployments/shared/components/unsupported-dsl-nodes-alert'
import { isDeploymentDslImportEnabled } from '@/features/deployments/shared/domain/feature-flags'
import { useInfiniteScroll } from '@/features/deployments/shared/hooks/use-infinite-scroll'
import { StepShell } from './layout'
const sourceAppSkeletonKeys = ['first-source-app', 'second-source-app', 'third-source-app']
export function SourceStepContent() {
const method = useAtomValue(effectiveMethodAtom)
const unsupportedDslNodes = useAtomValue(unsupportedDslNodesAtom)
return (
{method === 'bindApp' && }
{method === 'importDsl' && }
)
}
function SourceMethodSection() {
const { t } = useTranslation('deployments')
const method = useAtomValue(effectiveMethodAtom)
const selectMethod = useSetAtom(selectMethodAtom)
return (
$['createGuide.steps.method'])}
description={t(($) => $['createGuide.method.description'])}
descriptionClassName="lg:hidden"
hideHeader
>
value={method}
onValueChange={selectMethod}
className="flex flex-col items-stretch gap-2 sm:flex-row"
>
$['createGuide.methods.bindApp.title'])}
description={t(($) => $['createGuide.methods.bindApp.description'])}
/>
{isDeploymentDslImportEnabled && (
$['createGuide.methods.importDsl.title'])}
description={t(($) => $['createGuide.methods.importDsl.description'])}
/>
)}
)
}
function SourceMethodCard({
value,
icon,
title,
description,
badge,
}: {
value: GuideMethod
icon: string
title: string
description: string
badge?: string
}) {
return (
value={value}
nativeButton
render={}
className={cn(
`relative box-content h-[84px] w-full cursor-pointer rounded-xl border-[0.5px] border-components-option-card-option-border bg-components-panel-on-panel-item-bg p-3 text-left shadow-xs outline-hidden hover:shadow-md focus-visible:ring-2 focus-visible:ring-state-accent-solid sm:w-[240px]`,
'data-checked:border-components-option-card-option-selected-border data-checked:bg-components-option-card-option-selected-bg data-checked:shadow-md data-checked:inset-ring-[0.5px] data-checked:inset-ring-components-option-card-option-selected-border',
)}
>
{title}
{badge && (
{badge}
)}
{description}
)
}
function SourceAppSelectionSection() {
const { t } = useTranslation('deployments')
return (
$['createGuide.source.title'])}
description={t(($) => $['createGuide.source.description'])}
descriptionClassName="lg:hidden"
hideHeader
className="min-h-0 flex-1"
>
)
}
function SourceSearchInput() {
const { t } = useTranslation('deployments')
const sourceSearchText = useAtomValue(sourceSearchTextAtom)
const setSourceSearchText = useSetAtom(setSourceSearchTextAtom)
return (
$['createGuide.source.sourceApp'])}
value={sourceSearchText}
onChange={(event) => setSourceSearchText(event.target.value)}
placeholder={t(($) => $['createGuide.source.searchPlaceholder'])}
className="h-9 pr-8 pl-8"
/>
{sourceSearchText && (
)}
)
}
function SourceAppList() {
const { t } = useTranslation('deployments')
const selectSourceApp = useSetAtom(selectSourceAppAtom)
const effectiveSelectedApp = useAtomValue(effectiveSelectedAppAtom)
const sourceApps = useAtomValue(sourceAppsAtom)
const sourceAppsError = useAtomValue(sourceAppsErrorAtom)
const sourceAppsFetchNextPage = useAtomValue(sourceAppsFetchNextPageAtom)
const sourceAppsHasNextPage = useAtomValue(sourceAppsHasNextPageAtom)
const sourceAppsIsFetching = useAtomValue(sourceAppsIsFetchingAtom)
const sourceAppsIsFetchingNextPage = useAtomValue(sourceAppsIsFetchingNextPageAtom)
const sourceAppsIsLoading = useAtomValue(sourceAppsIsLoadingAtom)
const sourceAppsIsPlaceholderData = useAtomValue(sourceAppsIsPlaceholderDataAtom)
const sourceAppsLoading =
sourceAppsIsLoading ||
sourceAppsIsPlaceholderData ||
(sourceAppsIsFetching && sourceApps.length === 0)
const { rootRef, sentinelRef } = useInfiniteScroll(
{
error: sourceAppsError,
fetchNextPage: sourceAppsFetchNextPage,
hasNextPage: sourceAppsHasNextPage,
isFetching: sourceAppsIsFetching,
isFetchingNextPage: sourceAppsIsFetchingNextPage,
isLoading: sourceAppsIsLoading,
},
{
enabled: !sourceAppsLoading,
rootMargin: '0px 0px 160px 0px',
threshold: 0.1,
},
)
return (
{sourceAppsLoading ? (
) : sourceApps.length === 0 ? (
{t(($) => $['createGuide.source.empty'])}
) : (
{sourceApps.map((app) => (
selectSourceApp(app)}
/>
))}
{sourceAppsIsFetchingNextPage && (
{t(($) => $['createModal.loadingApps'])}
)}
{sourceAppsHasNextPage && }
)}
)
}
function SourceAppSkeleton() {
return (
{sourceAppSkeletonKeys.map((key) => (
))}
)
}
function SourceAppOption({
app,
onSelect,
selected,
}: {
app: WorkflowSourceApp
onSelect: () => void
selected: boolean
}) {
return (
)
}
function DslUploadSection() {
const { t } = useTranslation('deployments')
const dslFile = useAtomValue(dslFileAtom)
const selectDslFile = useSetAtom(selectDslFileAtom)
return (
$['createGuide.dsl.title'])}
description={t(($) => $['createGuide.dsl.description'])}
hideHeader
>
{t(($) => $['createGuide.dsl.dropTitle'])}
{t(($) => $['createGuide.dsl.dropDescription'])}
)
}
function DslReadStatus() {
const { t } = useTranslation('deployments')
const isReadingDsl = useAtomValue(isReadingDslAtom)
const dslReadError = useAtomValue(dslReadErrorAtom)
const dslUnsupportedMode = useAtomValue(dslUnsupportedModeAtom)
return (
<>
{isReadingDsl && (
{t(($) => $['createGuide.dsl.reading'])}
)}
{dslReadError && (
{t(($) => $['createGuide.dsl.readFailed'])}
)}
{dslUnsupportedMode && (
{t(($) => $['createGuide.dsl.unsupportedMode'])}
)}
>
)
}
export function SourceActionButtons() {
const { t } = useTranslation('deployments')
const canGoNext = useAtomValue(sourceCanGoNextAtom)
const continueFromSource = useSetAtom(continueFromSourceAtom)
return (
)
}