mirror of
https://github.com/langgenius/dify.git
synced 2026-08-28 19:16:51 +08:00
Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
263 lines
13 KiB
TypeScript
263 lines
13 KiB
TypeScript
import type { IconInfo } from '@/models/datasets'
|
|
import type { PublishWorkflowParams } from '@/types/workflow'
|
|
import { RiArrowRightUpLine, RiHammerLine, RiPlayCircleLine, RiTerminalBoxLine } from '@remixicon/react'
|
|
import { useBoolean, useKeyPress } from 'ahooks'
|
|
import { memo, useCallback, useState } from 'react'
|
|
import { Trans, useTranslation } from 'react-i18next'
|
|
import { trackEvent } from '@/app/components/base/amplitude'
|
|
import Divider from '@/app/components/base/divider'
|
|
import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
|
|
import PremiumBadge from '@/app/components/base/premium-badge'
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogActions,
|
|
AlertDialogCancelButton,
|
|
AlertDialogConfirmButton,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogTitle,
|
|
} from '@/app/components/base/ui/alert-dialog'
|
|
import { Button } from '@/app/components/base/ui/button'
|
|
import { toast } from '@/app/components/base/ui/toast'
|
|
import { useChecklistBeforePublish } from '@/app/components/workflow/hooks'
|
|
import ShortcutsName from '@/app/components/workflow/shortcuts-name'
|
|
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
|
import { getKeyboardKeyCodeBySystem } from '@/app/components/workflow/utils'
|
|
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
|
import { useDocLink } from '@/context/i18n'
|
|
import { useModalContextSelector } from '@/context/modal-context'
|
|
import { useProviderContextSelector } from '@/context/provider-context'
|
|
import { useDatasetApiAccessUrl } from '@/hooks/use-api-access-url'
|
|
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
|
import Link from '@/next/link'
|
|
import { useParams, useRouter } from '@/next/navigation'
|
|
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
|
|
import { useInvalid } from '@/service/use-base'
|
|
import { publishedPipelineInfoQueryKeyPrefix, useInvalidCustomizedTemplateList, usePublishAsCustomizedPipeline } from '@/service/use-pipeline'
|
|
import { usePublishWorkflow } from '@/service/use-workflow'
|
|
import { cn } from '@/utils/classnames'
|
|
import PublishAsKnowledgePipelineModal from '../../publish-as-knowledge-pipeline-modal'
|
|
|
|
const PUBLISH_SHORTCUT = ['ctrl', '⇧', 'P']
|
|
const Popup = () => {
|
|
const { t } = useTranslation()
|
|
const { datasetId } = useParams()
|
|
const { push } = useRouter()
|
|
const docLink = useDocLink()
|
|
const publishedAt = useStore(s => s.publishedAt)
|
|
const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
|
|
const pipelineId = useStore(s => s.pipelineId)
|
|
const mutateDatasetRes = useDatasetDetailContextWithSelector(s => s.mutateDatasetRes)
|
|
const [published, setPublished] = useState(false)
|
|
const { formatTimeFromNow } = useFormatTimeFromNow()
|
|
const { handleCheckBeforePublish } = useChecklistBeforePublish()
|
|
const { mutateAsync: publishWorkflow } = usePublishWorkflow()
|
|
const workflowStore = useWorkflowStore()
|
|
const isAllowPublishAsCustomKnowledgePipelineTemplate = useProviderContextSelector(s => s.isAllowPublishAsCustomKnowledgePipelineTemplate)
|
|
const setShowPricingModal = useModalContextSelector(s => s.setShowPricingModal)
|
|
const apiReferenceUrl = useDatasetApiAccessUrl()
|
|
const [confirmVisible, { setFalse: hideConfirm, setTrue: showConfirm }] = useBoolean(false)
|
|
const [publishing, { setFalse: hidePublishing, setTrue: showPublishing }] = useBoolean(false)
|
|
const { mutateAsync: publishAsCustomizedPipeline } = usePublishAsCustomizedPipeline()
|
|
const [showPublishAsKnowledgePipelineModal, { setFalse: hidePublishAsKnowledgePipelineModal, setTrue: setShowPublishAsKnowledgePipelineModal }] = useBoolean(false)
|
|
const [isPublishingAsCustomizedPipeline, { setFalse: hidePublishingAsCustomizedPipeline, setTrue: showPublishingAsCustomizedPipeline }] = useBoolean(false)
|
|
const invalidPublishedPipelineInfo = useInvalid([...publishedPipelineInfoQueryKeyPrefix, pipelineId])
|
|
const invalidDatasetList = useInvalidDatasetList()
|
|
const handlePublish = useCallback(async (params?: PublishWorkflowParams) => {
|
|
if (publishing)
|
|
return
|
|
try {
|
|
const checked = await handleCheckBeforePublish()
|
|
if (checked) {
|
|
if (!publishedAt && !confirmVisible) {
|
|
showConfirm()
|
|
return
|
|
}
|
|
showPublishing()
|
|
const res = await publishWorkflow({
|
|
url: `/rag/pipelines/${pipelineId}/workflows/publish`,
|
|
title: params?.title || '',
|
|
releaseNotes: params?.releaseNotes || '',
|
|
})
|
|
setPublished(true)
|
|
trackEvent('app_published_time', { action_mode: 'pipeline', app_id: datasetId, app_name: params?.title || '' })
|
|
if (res) {
|
|
toast.success(t('publishPipeline.success.message', { ns: 'datasetPipeline' }), {
|
|
description: (
|
|
<div className="system-xs-regular text-text-secondary">
|
|
<Trans
|
|
i18nKey="publishPipeline.success.tip"
|
|
ns="datasetPipeline"
|
|
components={{
|
|
CustomLink: (
|
|
<Link className="system-xs-medium text-text-accent" href={`/datasets/${datasetId}/documents`}>
|
|
</Link>
|
|
),
|
|
}}
|
|
/>
|
|
</div>
|
|
),
|
|
})
|
|
workflowStore.getState().setPublishedAt(res.created_at)
|
|
mutateDatasetRes?.()
|
|
invalidPublishedPipelineInfo()
|
|
invalidDatasetList()
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
toast.error(t('publishPipeline.error.message', { ns: 'datasetPipeline' }))
|
|
}
|
|
finally {
|
|
if (publishing)
|
|
hidePublishing()
|
|
if (confirmVisible)
|
|
hideConfirm()
|
|
}
|
|
}, [publishing, handleCheckBeforePublish, publishedAt, confirmVisible, showPublishing, publishWorkflow, pipelineId, datasetId, showConfirm, t, workflowStore, mutateDatasetRes, invalidPublishedPipelineInfo, invalidDatasetList, hidePublishing, hideConfirm])
|
|
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => {
|
|
e.preventDefault()
|
|
if (published)
|
|
return
|
|
handlePublish()
|
|
}, { exactMatch: true, useCapture: true })
|
|
const goToAddDocuments = useCallback(() => {
|
|
push(`/datasets/${datasetId}/documents/create-from-pipeline`)
|
|
}, [datasetId, push])
|
|
const invalidCustomizedTemplateList = useInvalidCustomizedTemplateList()
|
|
const handlePublishAsKnowledgePipeline = useCallback(async (name: string, icon: IconInfo, description?: string) => {
|
|
try {
|
|
showPublishingAsCustomizedPipeline()
|
|
await publishAsCustomizedPipeline({
|
|
pipelineId: pipelineId || '',
|
|
name,
|
|
icon_info: icon,
|
|
description,
|
|
})
|
|
toast.success(t('publishTemplate.success.message', { ns: 'datasetPipeline' }), {
|
|
description: (
|
|
<div className="flex flex-col gap-y-1">
|
|
<span className="system-xs-regular text-text-secondary">
|
|
{t('publishTemplate.success.tip', { ns: 'datasetPipeline' })}
|
|
</span>
|
|
<Link href={docLink()} target="_blank" className="inline-block system-xs-medium-uppercase text-text-accent">
|
|
{t('publishTemplate.success.learnMore', { ns: 'datasetPipeline' })}
|
|
</Link>
|
|
</div>
|
|
),
|
|
})
|
|
invalidCustomizedTemplateList()
|
|
}
|
|
catch {
|
|
toast.error(t('publishTemplate.error.message', { ns: 'datasetPipeline' }))
|
|
}
|
|
finally {
|
|
hidePublishingAsCustomizedPipeline()
|
|
hidePublishAsKnowledgePipelineModal()
|
|
}
|
|
}, [showPublishingAsCustomizedPipeline, publishAsCustomizedPipeline, pipelineId, t, invalidCustomizedTemplateList, hidePublishingAsCustomizedPipeline, hidePublishAsKnowledgePipelineModal])
|
|
const handleClickPublishAsKnowledgePipeline = useCallback(() => {
|
|
if (!isAllowPublishAsCustomKnowledgePipelineTemplate)
|
|
setShowPricingModal()
|
|
else
|
|
setShowPublishAsKnowledgePipelineModal()
|
|
}, [isAllowPublishAsCustomKnowledgePipelineTemplate, setShowPublishAsKnowledgePipelineModal, setShowPricingModal])
|
|
return (
|
|
<div className={cn('rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl shadow-shadow-shadow-5', isAllowPublishAsCustomKnowledgePipelineTemplate ? 'w-[360px]' : 'w-[400px]')}>
|
|
<div className="p-4 pt-3">
|
|
<div className="flex h-6 items-center system-xs-medium-uppercase text-text-tertiary">
|
|
{publishedAt ? t('common.latestPublished', { ns: 'workflow' }) : t('common.currentDraftUnpublished', { ns: 'workflow' })}
|
|
</div>
|
|
{publishedAt
|
|
? (
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center system-sm-medium text-text-secondary">
|
|
{t('common.publishedAt', { ns: 'workflow' })}
|
|
{' '}
|
|
{formatTimeFromNow(publishedAt)}
|
|
</div>
|
|
</div>
|
|
)
|
|
: (
|
|
<div className="flex items-center system-sm-medium text-text-secondary">
|
|
{t('common.autoSaved', { ns: 'workflow' })}
|
|
{' '}
|
|
·
|
|
{Boolean(draftUpdatedAt) && formatTimeFromNow(draftUpdatedAt!)}
|
|
</div>
|
|
)}
|
|
<Button variant="primary" className="mt-3 w-full" onClick={() => handlePublish()} disabled={published || publishing}>
|
|
{published
|
|
? t('common.published', { ns: 'workflow' })
|
|
: (
|
|
<div className="flex gap-1">
|
|
<span>{t('common.publishUpdate', { ns: 'workflow' })}</span>
|
|
<ShortcutsName keys={PUBLISH_SHORTCUT} bgColor="white" />
|
|
</div>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
<div className="border-t-[0.5px] border-t-divider-regular p-4 pt-3">
|
|
<Button className="mb-1 w-full hover:bg-state-accent-hover hover:text-text-accent" variant="tertiary" onClick={goToAddDocuments} disabled={!publishedAt}>
|
|
<div className="flex grow items-center">
|
|
<RiPlayCircleLine className="mr-2 h-4 w-4" />
|
|
{t('common.goToAddDocuments', { ns: 'pipeline' })}
|
|
</div>
|
|
<RiArrowRightUpLine className="ml-2 h-4 w-4 shrink-0" />
|
|
</Button>
|
|
<Link href={apiReferenceUrl} target="_blank" rel="noopener noreferrer">
|
|
<Button className="w-full hover:bg-state-accent-hover hover:text-text-accent" variant="tertiary" disabled={!publishedAt}>
|
|
<div className="flex grow items-center">
|
|
<RiTerminalBoxLine className="mr-2 h-4 w-4" />
|
|
{t('common.accessAPIReference', { ns: 'workflow' })}
|
|
</div>
|
|
<RiArrowRightUpLine className="ml-2 h-4 w-4 shrink-0" />
|
|
</Button>
|
|
</Link>
|
|
<Divider className="my-2" />
|
|
<Button className="w-full hover:bg-state-accent-hover hover:text-text-accent" variant="tertiary" onClick={handleClickPublishAsKnowledgePipeline} disabled={!publishedAt || isPublishingAsCustomizedPipeline}>
|
|
<div className="flex grow items-center gap-x-2 overflow-hidden">
|
|
<RiHammerLine className="h-4 w-4 shrink-0" />
|
|
<span className="grow truncate text-left" title={t('common.publishAs', { ns: 'pipeline' })}>
|
|
{t('common.publishAs', { ns: 'pipeline' })}
|
|
</span>
|
|
{!isAllowPublishAsCustomKnowledgePipelineTemplate && (
|
|
<PremiumBadge className="shrink-0 cursor-pointer select-none" size="s" color="indigo">
|
|
<SparklesSoft className="flex size-3 items-center text-components-premium-badge-indigo-text-stop-0" />
|
|
<span className="p-0.5 system-2xs-medium">
|
|
{t('upgradeBtn.encourageShort', { ns: 'billing' })}
|
|
</span>
|
|
</PremiumBadge>
|
|
)}
|
|
</div>
|
|
</Button>
|
|
</div>
|
|
<AlertDialog open={confirmVisible} onOpenChange={open => !open && hideConfirm()}>
|
|
<AlertDialogContent>
|
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
|
<AlertDialogTitle
|
|
title={t('common.confirmPublish', { ns: 'pipeline' })}
|
|
className="w-full truncate title-2xl-semi-bold text-text-primary"
|
|
>
|
|
{t('common.confirmPublish', { ns: 'pipeline' })}
|
|
</AlertDialogTitle>
|
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
|
{t('common.confirmPublishContent', { ns: 'pipeline' })}
|
|
</AlertDialogDescription>
|
|
</div>
|
|
<AlertDialogActions>
|
|
<AlertDialogCancelButton>
|
|
{t('operation.cancel', { ns: 'common' })}
|
|
</AlertDialogCancelButton>
|
|
<AlertDialogConfirmButton disabled={publishing} onClick={() => void handlePublish()}>
|
|
{t('operation.confirm', { ns: 'common' })}
|
|
</AlertDialogConfirmButton>
|
|
</AlertDialogActions>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
{showPublishAsKnowledgePipelineModal && (<PublishAsKnowledgePipelineModal confirmDisabled={isPublishingAsCustomizedPipeline} onConfirm={handlePublishAsKnowledgePipeline} onCancel={hidePublishAsKnowledgePipelineModal} />)}
|
|
</div>
|
|
)
|
|
}
|
|
export default memo(Popup)
|