mirror of https://github.com/langgenius/dify.git
workflow publish
This commit is contained in:
parent
7320ac41af
commit
4eb7546177
|
|
@ -9,9 +9,11 @@ import { Plus02 } from '@/app/components/base/icons/src/vender/line/general'
|
|||
|
||||
type ChooseFeatureProps = {
|
||||
onChange?: OnFeaturesChange
|
||||
disabled?: boolean
|
||||
}
|
||||
const ChooseFeature = ({
|
||||
onChange,
|
||||
disabled,
|
||||
}: ChooseFeatureProps) => {
|
||||
const { t } = useTranslation()
|
||||
const showFeaturesModal = useFeatures(s => s.showFeaturesModal)
|
||||
|
|
@ -19,8 +21,11 @@ const ChooseFeature = ({
|
|||
return (
|
||||
<>
|
||||
<Button
|
||||
className='px-3 py-0 h-8 rounded-lg border border-primary-100 bg-primary-25 shadow-xs text-xs font-semibold text-primary-600'
|
||||
onClick={() => setShowFeaturesModal(true)}
|
||||
className={`
|
||||
px-3 py-0 h-8 rounded-lg border border-primary-100 bg-primary-25 shadow-xs text-xs font-semibold text-primary-600
|
||||
${disabled && 'cursor-not-allowed opacity-50'}
|
||||
`}
|
||||
onClick={() => !disabled && setShowFeaturesModal(true)}
|
||||
>
|
||||
<Plus02 className='mr-1 w-4 h-4' />
|
||||
{t('appDebug.operation.addFeature')}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ import { File05 } from '@/app/components/base/icons/src/vender/solid/files'
|
|||
|
||||
type FileUploadProps = {
|
||||
onChange?: OnFeaturesChange
|
||||
disabled?: boolean
|
||||
}
|
||||
const FileUpload = ({
|
||||
onChange,
|
||||
disabled,
|
||||
}: FileUploadProps) => {
|
||||
const { t } = useTranslation()
|
||||
const featuresStore = useFeaturesStore()
|
||||
|
|
@ -45,11 +47,12 @@ const FileUpload = ({
|
|||
</div>
|
||||
<div className='grow' />
|
||||
<div className='flex items-center'>
|
||||
<ParamConfig onChange={onChange} />
|
||||
<ParamConfig onChange={onChange} disabled={disabled} />
|
||||
<div className='ml-4 mr-3 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Switch
|
||||
defaultValue={file.image.enabled}
|
||||
onChange={handleSwitch}
|
||||
disabled={disabled}
|
||||
size='md'
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@ import {
|
|||
|
||||
type ParamsConfigProps = {
|
||||
onChange?: OnFeaturesChange
|
||||
disabled?: boolean
|
||||
}
|
||||
const ParamsConfig = ({
|
||||
onChange,
|
||||
disabled,
|
||||
}: ParamsConfigProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
|
@ -30,8 +32,8 @@ const ParamsConfig = ({
|
|||
mainAxis: 4,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<div className={cn('flex items-center rounded-md h-7 px-3 space-x-1 text-gray-700 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')}>
|
||||
<PortalToFollowElemTrigger onClick={() => !disabled && setOpen(v => !v)}>
|
||||
<div className={cn('flex items-center rounded-md h-7 px-3 space-x-1 text-gray-700 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200', disabled && 'cursor-not-allowed opacity-50')}>
|
||||
<Settings01 className='w-3.5 h-3.5 ' />
|
||||
<div className='ml-1 leading-[18px] text-xs font-medium '>{t('appDebug.voice.settings')}</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ import Moderation from './moderation'
|
|||
export type FeaturePanelProps = {
|
||||
onChange?: OnFeaturesChange
|
||||
openingStatementProps: OpeningStatementProps
|
||||
disabled?: boolean
|
||||
}
|
||||
const FeaturePanel = ({
|
||||
onChange,
|
||||
openingStatementProps,
|
||||
disabled,
|
||||
}: FeaturePanelProps) => {
|
||||
const { t } = useTranslation()
|
||||
const features = useFeatures(s => s.features)
|
||||
|
|
@ -35,7 +37,10 @@ const FeaturePanel = ({
|
|||
|
||||
return (
|
||||
<div className='space-y-3'>
|
||||
<FileUpload onChange={onChange} />
|
||||
<FileUpload
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{
|
||||
showAdvanceFeature && (
|
||||
<div>
|
||||
|
|
@ -51,7 +56,11 @@ const FeaturePanel = ({
|
|||
<div className='py-2 space-y-2'>
|
||||
{
|
||||
features.opening.enabled && (
|
||||
<OpeningStatement {...openingStatementProps} onChange={onChange} />
|
||||
<OpeningStatement
|
||||
{...openingStatementProps}
|
||||
onChange={onChange}
|
||||
readonly={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
|
|
@ -61,7 +70,7 @@ const FeaturePanel = ({
|
|||
}
|
||||
{
|
||||
features.text2speech.enabled && (
|
||||
<TextToSpeech onChange={onChange} />
|
||||
<TextToSpeech onChange={onChange} disabled={disabled} />
|
||||
)
|
||||
}
|
||||
{
|
||||
|
|
@ -93,7 +102,7 @@ const FeaturePanel = ({
|
|||
<div className='py-2 space-y-2'>
|
||||
{
|
||||
features.moderation.enabled && (
|
||||
<Moderation onChange={onChange} />
|
||||
<Moderation onChange={onChange} disabled={disabled} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ import I18n from '@/context/i18n'
|
|||
|
||||
type ModerationProps = {
|
||||
onChange?: OnFeaturesChange
|
||||
disabled?: boolean
|
||||
}
|
||||
const Moderation = ({
|
||||
onChange,
|
||||
disabled,
|
||||
}: ModerationProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { setShowModerationSettingModal } = useModalContext()
|
||||
|
|
@ -32,6 +34,9 @@ const Moderation = ({
|
|||
)
|
||||
|
||||
const handleOpenModerationSettingModal = () => {
|
||||
if (disabled)
|
||||
return
|
||||
|
||||
const {
|
||||
features,
|
||||
setFeatures,
|
||||
|
|
@ -89,6 +94,7 @@ const Moderation = ({
|
|||
className={`
|
||||
shrink-0 flex items-center px-3 h-7 cursor-pointer rounded-md
|
||||
text-xs text-gray-700 font-medium hover:bg-gray-200
|
||||
${disabled && '!cursor-not-allowed'}
|
||||
`}
|
||||
onClick={handleOpenModerationSettingModal}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ import AudioBtn from '@/app/components/base/audio-btn'
|
|||
|
||||
type TextToSpeechProps = {
|
||||
onChange?: OnFeaturesChange
|
||||
disabled?: boolean
|
||||
}
|
||||
const TextToSpeech = ({
|
||||
onChange,
|
||||
disabled,
|
||||
}: TextToSpeechProps) => {
|
||||
const { t } = useTranslation()
|
||||
const textToSpeech = useFeatures(s => s.features.text2speech)
|
||||
|
|
@ -50,7 +52,7 @@ const TextToSpeech = ({
|
|||
)}
|
||||
</div>
|
||||
<div className='shrink-0 flex items-center'>
|
||||
<ParamsConfig onChange={onChange} />
|
||||
<ParamsConfig onChange={onChange} disabled={disabled} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ import {
|
|||
|
||||
type ParamsConfigProps = {
|
||||
onChange?: OnFeaturesChange
|
||||
disabled?: boolean
|
||||
}
|
||||
const ParamsConfig = ({
|
||||
onChange,
|
||||
disabled,
|
||||
}: ParamsConfigProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
|
@ -29,7 +31,7 @@ const ParamsConfig = ({
|
|||
mainAxis: 4,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<PortalToFollowElemTrigger onClick={() => !disabled && setOpen(v => !v)}>
|
||||
<div className={cn('flex items-center rounded-md h-7 px-3 space-x-1 text-gray-700 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')}>
|
||||
<Settings01 className='w-3.5 h-3.5 ' />
|
||||
<div className='ml-1 leading-[18px] text-xs font-medium '>{t('appDebug.voice.settings')}</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="clock-refresh">
|
||||
<g id="Solid">
|
||||
<path d="M9.76984 2.8375L9.71551 3.04027C8.27602 1.22839 5.68891 0.69459 3.62457 1.88644C2.25681 2.67611 1.43067 4.04369 1.27558 5.50073C1.24636 5.77532 1.44526 6.02161 1.71985 6.05084C1.99444 6.08007 2.24074 5.88116 2.26997 5.60657C2.39268 4.4537 3.04533 3.37556 4.12456 2.75247C5.7025 1.84145 7.66731 2.20754 8.82211 3.53002L8.65016 3.48395C8.38343 3.41248 8.10926 3.57077 8.03779 3.8375C7.96632 4.10424 8.12461 4.37841 8.39134 4.44988L9.75737 4.8159C10.0241 4.88737 10.2983 4.72908 10.3697 4.46235L10.7358 3.09632C10.8072 2.82959 10.6489 2.55542 10.3822 2.48395C10.1155 2.41248 9.84131 2.57077 9.76984 2.8375Z" fill="#667085"/>
|
||||
<path d="M10.2792 5.94921C10.5538 5.97844 10.7527 6.22473 10.7235 6.49932C10.5684 7.95635 9.74225 9.32394 8.3745 10.1136C6.31011 11.3055 3.72295 10.7716 2.28347 8.95968L2.22918 9.1623C2.15771 9.42903 1.88354 9.58732 1.61681 9.51585C1.35008 9.44438 1.19178 9.17021 1.26325 8.90348L1.62928 7.53746C1.70075 7.27072 1.97492 7.11243 2.24165 7.1839L3.60768 7.54993C3.87441 7.6214 4.0327 7.89557 3.96123 8.1623C3.88976 8.42903 3.61559 8.58732 3.34886 8.51585L3.17668 8.46972C4.33144 9.79246 6.29644 10.1587 7.8745 9.24758C8.95373 8.62449 9.60638 7.54634 9.72909 6.39348C9.75832 6.11889 10.0046 5.91998 10.2792 5.94921Z" fill="#667085"/>
|
||||
<path d="M6.49954 3.74997C6.49954 3.47382 6.27568 3.24997 5.99954 3.24997C5.7234 3.24997 5.49954 3.47382 5.49954 3.74997V5.99997C5.49954 6.1756 5.59169 6.33835 5.74229 6.42871L6.99229 7.17871C7.22908 7.32079 7.53621 7.244 7.67828 7.00721C7.82036 6.77042 7.74358 6.46329 7.50679 6.32122L6.49954 5.71687V3.74997Z" fill="#667085"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "12",
|
||||
"height": "12",
|
||||
"viewBox": "0 0 12 12",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "clock-refresh"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Solid"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M9.76984 2.8375L9.71551 3.04027C8.27602 1.22839 5.68891 0.69459 3.62457 1.88644C2.25681 2.67611 1.43067 4.04369 1.27558 5.50073C1.24636 5.77532 1.44526 6.02161 1.71985 6.05084C1.99444 6.08007 2.24074 5.88116 2.26997 5.60657C2.39268 4.4537 3.04533 3.37556 4.12456 2.75247C5.7025 1.84145 7.66731 2.20754 8.82211 3.53002L8.65016 3.48395C8.38343 3.41248 8.10926 3.57077 8.03779 3.8375C7.96632 4.10424 8.12461 4.37841 8.39134 4.44988L9.75737 4.8159C10.0241 4.88737 10.2983 4.72908 10.3697 4.46235L10.7358 3.09632C10.8072 2.82959 10.6489 2.55542 10.3822 2.48395C10.1155 2.41248 9.84131 2.57077 9.76984 2.8375Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M10.2792 5.94921C10.5538 5.97844 10.7527 6.22473 10.7235 6.49932C10.5684 7.95635 9.74225 9.32394 8.3745 10.1136C6.31011 11.3055 3.72295 10.7716 2.28347 8.95968L2.22918 9.1623C2.15771 9.42903 1.88354 9.58732 1.61681 9.51585C1.35008 9.44438 1.19178 9.17021 1.26325 8.90348L1.62928 7.53746C1.70075 7.27072 1.97492 7.11243 2.24165 7.1839L3.60768 7.54993C3.87441 7.6214 4.0327 7.89557 3.96123 8.1623C3.88976 8.42903 3.61559 8.58732 3.34886 8.51585L3.17668 8.46972C4.33144 9.79246 6.29644 10.1587 7.8745 9.24758C8.95373 8.62449 9.60638 7.54634 9.72909 6.39348C9.75832 6.11889 10.0046 5.91998 10.2792 5.94921Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M6.49954 3.74997C6.49954 3.47382 6.27568 3.24997 5.99954 3.24997C5.7234 3.24997 5.49954 3.47382 5.49954 3.74997V5.99997C5.49954 6.1756 5.59169 6.33835 5.74229 6.42871L6.99229 7.17871C7.22908 7.32079 7.53621 7.244 7.67828 7.00721C7.82036 6.77042 7.74358 6.46329 7.50679 6.32122L6.49954 5.71687V3.74997Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "ClockRefresh"
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './ClockRefresh.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'ClockRefresh'
|
||||
|
||||
export default Icon
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
export { default as ClockFastForward } from './ClockFastForward'
|
||||
export { default as ClockPlay } from './ClockPlay'
|
||||
export { default as ClockRefresh } from './ClockRefresh'
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ import {
|
|||
const Features = () => {
|
||||
const { t } = useTranslation()
|
||||
const isChatMode = useIsChatMode()
|
||||
const setShowFeaturesPanel = useStore(state => state.setShowFeaturesPanel)
|
||||
const setShowFeaturesPanel = useStore(s => s.setShowFeaturesPanel)
|
||||
const isRestoring = useStore(s => s.isRestoring)
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
|
||||
const handleFeaturesChange = useCallback(() => {
|
||||
|
|
@ -32,7 +33,10 @@ const Features = () => {
|
|||
{
|
||||
isChatMode && (
|
||||
<>
|
||||
<FeaturesChoose onChange={handleFeaturesChange} />
|
||||
<FeaturesChoose
|
||||
disabled={isRestoring}
|
||||
onChange={handleFeaturesChange}
|
||||
/>
|
||||
<div className='mx-3 w-[1px] h-[14px] bg-gray-200'></div>
|
||||
</>
|
||||
)
|
||||
|
|
@ -47,6 +51,7 @@ const Features = () => {
|
|||
</div>
|
||||
<div className='p-4'>
|
||||
<FeaturesPanel
|
||||
disabled={isRestoring}
|
||||
onChange={handleFeaturesChange}
|
||||
openingStatementProps={{
|
||||
onAutoAddPromptVariable: () => {},
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { memo } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useWorkflow } from '../hooks'
|
||||
import { Edit03 } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
|
||||
const EditingTitle = () => {
|
||||
const { t } = useTranslation()
|
||||
const { formatTimeFromNow } = useWorkflow()
|
||||
const draftUpdatedAt = useStore(state => state.draftUpdatedAt)
|
||||
const publishedAt = useStore(state => state.publishedAt)
|
||||
|
||||
|
|
@ -14,17 +16,17 @@ const EditingTitle = () => {
|
|||
<Edit03 className='mr-1 w-3 h-3 text-gray-400' />
|
||||
{t('workflow.common.editing')}
|
||||
{
|
||||
draftUpdatedAt && (
|
||||
!!draftUpdatedAt && (
|
||||
<>
|
||||
<span className='flex items-center mx-1'>·</span>
|
||||
{t('workflow.common.autoSaved')} {dayjs(draftUpdatedAt * 1000).format('HH:mm:ss')}
|
||||
{t('workflow.common.autoSaved')} {dayjs(draftUpdatedAt).format('HH:mm:ss')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
<span className='flex items-center mx-1'>·</span>
|
||||
{
|
||||
publishedAt
|
||||
? `${t('workflow.common.published')} ${dayjs(publishedAt).fromNow()}`
|
||||
? `${t('workflow.common.published')} ${formatTimeFromNow(publishedAt)}`
|
||||
: t('workflow.common.unpublished')
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { useWorkflowRun } from '../hooks'
|
|||
import RunAndHistory from './run-and-history'
|
||||
import EditingTitle from './editing-title'
|
||||
import RunningTitle from './running-title'
|
||||
import RestoringTitle from './restoring-title'
|
||||
import Publish from './publish'
|
||||
import { Grid01 } from '@/app/components/base/icons/src/vender/line/layout'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
|
@ -24,7 +25,11 @@ const Header: FC = () => {
|
|||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const appSidebarExpand = useAppStore(s => s.appSidebarExpand)
|
||||
const runningStatus = useStore(s => s.runningStatus)
|
||||
const { handleRunSetting } = useWorkflowRun()
|
||||
const isRestoring = useStore(s => s.isRestoring)
|
||||
const {
|
||||
handleRunSetting,
|
||||
handleRestoreFromPublishedWorkflow,
|
||||
} = useWorkflowRun()
|
||||
|
||||
const handleShowFeatures = useCallback(() => {
|
||||
if (runningStatus)
|
||||
|
|
@ -37,6 +42,15 @@ const Header: FC = () => {
|
|||
handleRunSetting(true)
|
||||
}, [handleRunSetting])
|
||||
|
||||
const handleCancelRestore = useCallback(() => {
|
||||
workflowStore.setState({ isRestoring: false })
|
||||
}, [workflowStore])
|
||||
|
||||
const handleRestore = useCallback(() => {
|
||||
handleRestoreFromPublishedWorkflow()
|
||||
workflowStore.setState({ isRestoring: false })
|
||||
}, [handleRestoreFromPublishedWorkflow, workflowStore])
|
||||
|
||||
return (
|
||||
<div
|
||||
className='absolute top-0 left-0 flex items-center justify-between px-3 w-full h-14 z-10'
|
||||
|
|
@ -51,42 +65,80 @@ const Header: FC = () => {
|
|||
)
|
||||
}
|
||||
{
|
||||
!runningStatus && <EditingTitle />
|
||||
!runningStatus && !isRestoring && <EditingTitle />
|
||||
}
|
||||
{
|
||||
runningStatus && <RunningTitle />
|
||||
runningStatus && !isRestoring && <RunningTitle />
|
||||
}
|
||||
{
|
||||
isRestoring && <RestoringTitle />
|
||||
}
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
{
|
||||
runningStatus && (
|
||||
{
|
||||
!isRestoring && (
|
||||
<div className='flex items-center'>
|
||||
{
|
||||
runningStatus && (
|
||||
<Button
|
||||
className={`
|
||||
mr-2 px-3 py-0 h-8 bg-white text-[13px] font-medium text-primary-600
|
||||
border-[0.5px] border-gray-200 shadow-xs
|
||||
`}
|
||||
onClick={handleGoBackToEdit}
|
||||
>
|
||||
<ArrowNarrowLeft className='mr-1 w-4 h-4' />
|
||||
{t('workflow.common.goBackToEdit')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<RunAndHistory />
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Button
|
||||
className={`
|
||||
mr-2 px-3 py-0 h-8 bg-white text-[13px] font-medium text-primary-600
|
||||
mr-2 px-3 py-0 h-8 bg-white text-[13px] font-medium text-gray-700
|
||||
border-[0.5px] border-gray-200 shadow-xs
|
||||
${runningStatus && '!cursor-not-allowed opacity-50'}
|
||||
`}
|
||||
onClick={handleGoBackToEdit}
|
||||
onClick={handleShowFeatures}
|
||||
>
|
||||
<ArrowNarrowLeft className='mr-1 w-4 h-4' />
|
||||
{t('workflow.common.goBackToEdit')}
|
||||
<Grid01 className='mr-1 w-4 h-4 text-gray-500' />
|
||||
{t('workflow.common.features')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<RunAndHistory />
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Button
|
||||
className={`
|
||||
mr-2 px-3 py-0 h-8 bg-white text-[13px] font-medium text-gray-700
|
||||
border-[0.5px] border-gray-200 shadow-xs
|
||||
${runningStatus && '!cursor-not-allowed opacity-50'}
|
||||
`}
|
||||
onClick={handleShowFeatures}
|
||||
>
|
||||
<Grid01 className='mr-1 w-4 h-4 text-gray-500' />
|
||||
{t('workflow.common.features')}
|
||||
</Button>
|
||||
<Publish />
|
||||
</div>
|
||||
<Publish />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
isRestoring && (
|
||||
<div className='flex items-center'>
|
||||
<Button
|
||||
className={`
|
||||
px-3 py-0 h-8 bg-white text-[13px] font-medium text-gray-700
|
||||
border-[0.5px] border-gray-200 shadow-xs
|
||||
${runningStatus && '!cursor-not-allowed opacity-50'}
|
||||
`}
|
||||
onClick={handleShowFeatures}
|
||||
>
|
||||
<Grid01 className='mr-1 w-4 h-4 text-gray-500' />
|
||||
{t('workflow.common.features')}
|
||||
</Button>
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Button
|
||||
className='mr-2 px-3 py-0 h-8 bg-white text-[13px] text-gray-700 font-medium border-[0.5px] border-gray-200 shadow-xs'
|
||||
onClick={handleCancelRestore}
|
||||
>
|
||||
{t('common.operation.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
className='px-3 py-0 h-8 text-[13px] font-medium shadow-xs'
|
||||
onClick={handleRestore}
|
||||
type='primary'
|
||||
>
|
||||
{t('workflow.common.restore')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -7,6 +8,7 @@ import {
|
|||
useStore,
|
||||
useWorkflowStore,
|
||||
} from '../store'
|
||||
import { useWorkflow } from '../hooks'
|
||||
import Button from '@/app/components/base/button'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
|
|
@ -18,8 +20,12 @@ import { useStore as useAppStore } from '@/app/components/app/store'
|
|||
|
||||
const Publish = () => {
|
||||
const { t } = useTranslation()
|
||||
const [published, setPublished] = useState(false)
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { formatTimeFromNow } = useWorkflow()
|
||||
const runningStatus = useStore(s => s.runningStatus)
|
||||
const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
|
||||
const publishedAt = useStore(s => s.publishedAt)
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const handlePublish = async () => {
|
||||
|
|
@ -27,13 +33,34 @@ const Publish = () => {
|
|||
try {
|
||||
const res = await publishWorkflow(`/apps/${appId}/workflows/publish`)
|
||||
|
||||
if (res)
|
||||
workflowStore.setState({ publishedAt: res.created_at })
|
||||
if (res) {
|
||||
setPublished(true)
|
||||
workflowStore.getState().setPublishedAt(res.created_at)
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
setPublished(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRestore = useCallback(() => {
|
||||
workflowStore.getState().setIsRestoring(true)
|
||||
setOpen(false)
|
||||
}, [workflowStore])
|
||||
|
||||
const handleTrigger = useCallback(() => {
|
||||
if (runningStatus)
|
||||
return
|
||||
|
||||
if (open)
|
||||
setOpen(false)
|
||||
|
||||
if (!open) {
|
||||
setOpen(true)
|
||||
setPublished(false)
|
||||
}
|
||||
}, [runningStatus, open])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
|
|
@ -44,12 +71,7 @@ const Publish = () => {
|
|||
crossAxis: -5,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => {
|
||||
if (runningStatus)
|
||||
return
|
||||
|
||||
setOpen(v => !v)
|
||||
}}>
|
||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||
<Button
|
||||
type='primary'
|
||||
className={`
|
||||
|
|
@ -67,29 +89,44 @@ const Publish = () => {
|
|||
{t('workflow.common.currentDraft').toLocaleUpperCase()}
|
||||
</div>
|
||||
<div className='flex items-center h-[18px] text-[13px] font-medium text-gray-700'>
|
||||
{t('workflow.common.autoSaved')} 3 min ago · Evan
|
||||
{t('workflow.common.autoSaved')} {formatTimeFromNow(draftUpdatedAt)}
|
||||
</div>
|
||||
<Button
|
||||
type='primary'
|
||||
className='mt-3 px-3 py-0 w-full h-8 border-[0.5px] border-primary-700 rounded-lg text-[13px] font-medium'
|
||||
className={`
|
||||
mt-3 px-3 py-0 w-full h-8 border-[0.5px] border-primary-700 rounded-lg text-[13px] font-medium
|
||||
${published && 'border-transparent'}
|
||||
`}
|
||||
onClick={handlePublish}
|
||||
disabled={published}
|
||||
>
|
||||
{t('workflow.common.publish')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className='p-4 pt-3 border-t-[0.5px] border-t-black/5'>
|
||||
<div className='flex items-center h-6 text-xs font-medium text-gray-500'>
|
||||
{t('workflow.common.latestPublished').toLocaleUpperCase()}
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<div className='flex items-center mt-[3px] mb-[3px] leading-[18px] text-[13px] font-medium text-gray-700'>
|
||||
{t('workflow.common.autoSaved')} 3 min ago · Evan
|
||||
{
|
||||
!!publishedAt && (
|
||||
<div className='p-4 pt-3 border-t-[0.5px] border-t-black/5'>
|
||||
<div className='flex items-center h-6 text-xs font-medium text-gray-500'>
|
||||
{t('workflow.common.latestPublished').toLocaleUpperCase()}
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<div className='flex items-center mt-[3px] mb-[3px] leading-[18px] text-[13px] font-medium text-gray-700'>
|
||||
{t('workflow.common.autoSaved')} {formatTimeFromNow(publishedAt)}
|
||||
</div>
|
||||
<Button
|
||||
className={`
|
||||
ml-2 px-2 py-0 h-6 shadow-xs rounded-md text-xs font-medium text-gray-700 border-[0.5px] border-gray-200
|
||||
${published && 'opacity-50 border-transparent shadow-none bg-transparent'}
|
||||
`}
|
||||
onClick={handleRestore}
|
||||
disabled={published}
|
||||
>
|
||||
{t('workflow.common.restore')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button className='ml-2 px-2 py-0 h-6 shadow-xs rounded-md text-xs font-medium text-gray-700 border-[0.5px] border-gray-200'>
|
||||
{t('workflow.common.restore')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useWorkflow } from '../hooks'
|
||||
import { useStore } from '../store'
|
||||
import { ClockRefresh } from '@/app/components/base/icons/src/vender/line/time'
|
||||
|
||||
const RestoringTitle = () => {
|
||||
const { t } = useTranslation()
|
||||
const { formatTimeFromNow } = useWorkflow()
|
||||
const publishedAt = useStore(state => state.publishedAt)
|
||||
|
||||
return (
|
||||
<div className='flex items-center h-[18px] text-xs text-gray-500'>
|
||||
<ClockRefresh className='mr-1 w-3 h-3 text-gray-500' />
|
||||
{t('workflow.common.latestPublished')}
|
||||
{formatTimeFromNow(publishedAt)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(RestoringTitle)
|
||||
|
|
@ -62,7 +62,7 @@ export const useNodesSyncDraft = () => {
|
|||
},
|
||||
},
|
||||
}).then((res) => {
|
||||
workflowStore.setState({ draftUpdatedAt: res.updated_at })
|
||||
workflowStore.getState().setDraftUpdatedAt(res.updated_at)
|
||||
})
|
||||
}
|
||||
}, [store, reactFlow, featuresStore, workflowStore])
|
||||
|
|
@ -73,9 +73,12 @@ export const useNodesSyncDraft = () => {
|
|||
})
|
||||
|
||||
const handleSyncWorkflowDraft = useCallback((shouldDelay?: boolean) => {
|
||||
const { runningStatus } = workflowStore.getState()
|
||||
const {
|
||||
runningStatus,
|
||||
isRestoring,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (runningStatus)
|
||||
if (runningStatus || isRestoring)
|
||||
return
|
||||
|
||||
if (shouldDelay)
|
||||
|
|
|
|||
|
|
@ -12,12 +12,17 @@ import {
|
|||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import type { IOtherOptions } from '@/service/base'
|
||||
import { ssePost } from '@/service/base'
|
||||
import { stopWorkflowRun } from '@/service/workflow'
|
||||
import {
|
||||
fetchPublishedWorkflow,
|
||||
stopWorkflowRun,
|
||||
} from '@/service/workflow'
|
||||
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
|
||||
export const useWorkflowRun = () => {
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const reactflow = useReactFlow()
|
||||
const featuresStore = useFeaturesStore()
|
||||
|
||||
const handleBackupDraft = useCallback(() => {
|
||||
const {
|
||||
|
|
@ -178,10 +183,34 @@ export const useWorkflowRun = () => {
|
|||
stopWorkflowRun(`/apps/${appId}/workflow-runs/tasks/${taskId}/stop`)
|
||||
}, [workflowStore])
|
||||
|
||||
const handleRestoreFromPublishedWorkflow = useCallback(async () => {
|
||||
const appDetail = useAppStore.getState().appDetail
|
||||
const publishedWorkflow = await fetchPublishedWorkflow(`/apps/${appDetail?.id}/workflows/publish`)
|
||||
|
||||
if (publishedWorkflow) {
|
||||
const {
|
||||
setNodes,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
const { setViewport } = reactflow
|
||||
const nodes = publishedWorkflow.graph.nodes
|
||||
const edges = publishedWorkflow.graph.edges
|
||||
const viewport = publishedWorkflow.graph.viewport
|
||||
|
||||
setNodes(nodes)
|
||||
setEdges(edges)
|
||||
if (viewport)
|
||||
setViewport(viewport)
|
||||
featuresStore?.setState({ features: publishedWorkflow.features })
|
||||
workflowStore.getState().setPublishedAt(publishedWorkflow.created_at)
|
||||
}
|
||||
}, [store, reactflow, featuresStore, workflowStore])
|
||||
|
||||
return {
|
||||
handleBackupDraft,
|
||||
handleRunSetting,
|
||||
handleRun,
|
||||
handleStopRun,
|
||||
handleRestoreFromPublishedWorkflow,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import {
|
|||
useCallback,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import { uniqBy } from 'lodash-es'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import useSWR from 'swr'
|
||||
import produce from 'immer'
|
||||
import {
|
||||
|
|
@ -33,10 +35,12 @@ import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
|||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import {
|
||||
fetchNodesDefaultConfigs,
|
||||
fetchPublishedWorkflow,
|
||||
fetchWorkflowDraft,
|
||||
syncWorkflowDraft,
|
||||
} from '@/service/workflow'
|
||||
import { fetchCollectionList } from '@/service/tools'
|
||||
import I18n from '@/context/i18n'
|
||||
|
||||
export const useIsChatMode = () => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
|
|
@ -45,6 +49,7 @@ export const useIsChatMode = () => {
|
|||
}
|
||||
|
||||
export const useWorkflow = () => {
|
||||
const { locale } = useContext(I18n)
|
||||
const store = useStoreApi()
|
||||
const reactflow = useReactFlow()
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
|
@ -211,12 +216,17 @@ export const useWorkflow = () => {
|
|||
return true
|
||||
}, [store, nodesExtraData])
|
||||
|
||||
const formatTimeFromNow = useCallback((time: number) => {
|
||||
return dayjs(time).locale(locale === 'zh-Hans' ? 'zh-cn' : locale).fromNow()
|
||||
}, [locale])
|
||||
|
||||
return {
|
||||
handleLayout,
|
||||
getTreeLeafNodes,
|
||||
getBeforeNodesInSameBranch,
|
||||
getAfterNodesInSameBranch,
|
||||
isValidConnection,
|
||||
formatTimeFromNow,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -226,10 +236,11 @@ export const useWorkflowInit = () => {
|
|||
const appDetail = useAppStore(state => state.appDetail)!
|
||||
const { data, error, mutate } = useSWR(`/apps/${appDetail.id}/workflows/draft`, fetchWorkflowDraft)
|
||||
|
||||
const handleFetchPreloadData = async () => {
|
||||
const handleFetchPreloadData = useCallback(async () => {
|
||||
try {
|
||||
const toolsets = await fetchCollectionList()
|
||||
const nodesDefaultConfigsData = await fetchNodesDefaultConfigs(`/apps/${appDetail?.id}/workflows/default-workflow-block-configs`)
|
||||
const publishedWorkflow = await fetchPublishedWorkflow(`/apps/${appDetail?.id}/workflows/publish`)
|
||||
|
||||
workflowStore.setState({
|
||||
toolsets,
|
||||
|
|
@ -245,19 +256,20 @@ export const useWorkflowInit = () => {
|
|||
return acc
|
||||
}, {} as Record<string, any>),
|
||||
})
|
||||
workflowStore.getState().setPublishedAt(publishedWorkflow?.created_at)
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
}, [workflowStore, appDetail])
|
||||
|
||||
useEffect(() => {
|
||||
handleFetchPreloadData()
|
||||
}, [])
|
||||
}, [handleFetchPreloadData])
|
||||
|
||||
useEffect(() => {
|
||||
if (data)
|
||||
workflowStore.setState({ draftUpdatedAt: data.updated_at })
|
||||
workflowStore.getState().setDraftUpdatedAt(data.updated_at)
|
||||
}, [data, workflowStore])
|
||||
|
||||
if (error && error.json && !error.bodyUsed && appDetail) {
|
||||
|
|
@ -280,7 +292,7 @@ export const useWorkflowInit = () => {
|
|||
features: {},
|
||||
},
|
||||
}).then((res) => {
|
||||
workflowStore.setState({ draftUpdatedAt: res.updated_at })
|
||||
workflowStore.getState().setDraftUpdatedAt(res.updated_at)
|
||||
mutate()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { memo } from 'react'
|
||||
import cn from 'classnames'
|
||||
import dayjs from 'dayjs'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSWR from 'swr'
|
||||
import { WorkflowRunningStatus } from '../types'
|
||||
import { useIsChatMode } from '../hooks'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useWorkflow,
|
||||
} from '../hooks'
|
||||
import { CheckCircle, XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { AlertCircle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
|
||||
import {
|
||||
|
|
@ -19,6 +21,7 @@ const RunHistory = () => {
|
|||
const { t } = useTranslation()
|
||||
const isChatMode = useIsChatMode()
|
||||
const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore()
|
||||
const { formatTimeFromNow } = useWorkflow()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const workflowRunId = useRunHistoryStore(state => state.workflowRunId)
|
||||
const { data: runList, isLoading: runListLoading } = useSWR((appDetail && !isChatMode) ? `/apps/${appDetail.id}/workflow-runs` : null, fetchWorkflowRunHistory)
|
||||
|
|
@ -93,7 +96,7 @@ const RunHistory = () => {
|
|||
{`Test ${isChatMode ? 'Chat' : 'Run'}#${item.sequence_number}`}
|
||||
</div>
|
||||
<div className='flex items-center text-xs text-gray-500 leading-[18px]'>
|
||||
{item.created_by_account.name} · {dayjs((item.finished_at || item.created_at) * 1000).fromNow()}
|
||||
{item.created_by_account.name} · {formatTimeFromNow((item.finished_at || item.created_at) * 1000)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useContext } from 'react'
|
||||
import {
|
||||
create,
|
||||
useStore as useZustandStore,
|
||||
} from 'zustand'
|
||||
import type { Viewport } from 'reactflow'
|
||||
import { useContext } from 'react'
|
||||
import type {
|
||||
HelpLineHorizontalPosition,
|
||||
HelpLineVerticalPosition,
|
||||
|
|
@ -44,6 +44,7 @@ type State = {
|
|||
notInitialWorkflow: boolean
|
||||
nodesDefaultConfigs: Record<string, any>
|
||||
nodeAnimation: boolean
|
||||
isRestoring: boolean
|
||||
}
|
||||
|
||||
type Action = {
|
||||
|
|
@ -66,6 +67,7 @@ type Action = {
|
|||
setNotInitialWorkflow: (notInitialWorkflow: boolean) => void
|
||||
setNodesDefaultConfigs: (nodesDefaultConfigs: Record<string, any>) => void
|
||||
setNodeAnimation: (nodeAnimation: boolean) => void
|
||||
setIsRestoring: (isRestoring: boolean) => void
|
||||
}
|
||||
|
||||
export const createWorkflowStore = () => {
|
||||
|
|
@ -91,9 +93,9 @@ export const createWorkflowStore = () => {
|
|||
toolsMap: {},
|
||||
setToolsMap: toolsMap => set(() => ({ toolsMap })),
|
||||
draftUpdatedAt: 0,
|
||||
setDraftUpdatedAt: draftUpdatedAt => set(() => ({ draftUpdatedAt })),
|
||||
setDraftUpdatedAt: draftUpdatedAt => set(() => ({ draftUpdatedAt: draftUpdatedAt ? draftUpdatedAt * 1000 : 0 })),
|
||||
publishedAt: 0,
|
||||
setPublishedAt: publishedAt => set(() => ({ publishedAt })),
|
||||
setPublishedAt: publishedAt => set(() => ({ publishedAt: publishedAt ? publishedAt * 1000 : 0 })),
|
||||
runningStatus: undefined,
|
||||
setRunningStatus: runningStatus => set(() => ({ runningStatus })),
|
||||
showInputsPanel: false,
|
||||
|
|
@ -108,6 +110,8 @@ export const createWorkflowStore = () => {
|
|||
setNodesDefaultConfigs: nodesDefaultConfigs => set(() => ({ nodesDefaultConfigs })),
|
||||
nodeAnimation: false,
|
||||
setNodeAnimation: nodeAnimation => set(() => ({ nodeAnimation })),
|
||||
isRestoring: false,
|
||||
setIsRestoring: isRestoring => set(() => ({ isRestoring })),
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ export const publishWorkflow = (url: string) => {
|
|||
return post<CommonResponse & { created_at: number }>(url)
|
||||
}
|
||||
|
||||
export const fetchPublishedWorkflow: Fetcher<FetchWorkflowDraftResponse, string> = (url) => {
|
||||
return get<FetchWorkflowDraftResponse>(url)
|
||||
}
|
||||
|
||||
export const stopWorkflowRun = (url: string) => {
|
||||
return post<CommonResponse>(url)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@ export type FetchWorkflowDraftResponse = {
|
|||
viewport?: Viewport
|
||||
}
|
||||
features?: any
|
||||
created_at: number
|
||||
created_by: {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
}
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue