mirror of
https://gitee.com/dify_ai/dify.git
synced 2024-12-01 10:48:37 +08:00
feat: workflow remove preview mode (#3941)
This commit is contained in:
parent
0940f01634
commit
8e4989ed03
@ -0,0 +1,5 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Left Icon">
|
||||
<path id="Vector" d="M7.83333 2.66683H5.7C4.5799 2.66683 4.01984 2.66683 3.59202 2.88482C3.21569 3.07656 2.90973 3.38252 2.71799 3.75885C2.5 4.18667 2.5 4.74672 2.5 5.86683V9.3335C2.5 9.95348 2.5 10.2635 2.56815 10.5178C2.75308 11.208 3.29218 11.7471 3.98236 11.932C4.2367 12.0002 4.54669 12.0002 5.16667 12.0002V13.5572C5.16667 13.9124 5.16667 14.09 5.23949 14.1812C5.30282 14.2606 5.39885 14.3067 5.50036 14.3066C5.61708 14.3065 5.75578 14.1955 6.03317 13.9736L7.62348 12.7014C7.94834 12.4415 8.11078 12.3115 8.29166 12.2191C8.45213 12.1371 8.62295 12.0772 8.79948 12.041C8.99845 12.0002 9.20646 12.0002 9.6225 12.0002H10.6333C11.7534 12.0002 12.3135 12.0002 12.7413 11.7822C13.1176 11.5904 13.4236 11.2845 13.6153 10.9081C13.8333 10.4803 13.8333 9.92027 13.8333 8.80016V8.66683M11.6551 6.472L14.8021 4.44889C15.0344 4.29958 15.1505 4.22493 15.1906 4.13C15.2257 4.04706 15.2257 3.95347 15.1906 3.87052C15.1505 3.7756 15.0344 3.70094 14.8021 3.55163L11.6551 1.52852C11.3874 1.35646 11.2536 1.27043 11.1429 1.27833C11.0465 1.28522 10.9578 1.33365 10.8998 1.41105C10.8333 1.49987 10.8333 1.65896 10.8333 1.97715V6.02337C10.8333 6.34156 10.8333 6.50066 10.8998 6.58948C10.9578 6.66688 11.0465 6.71531 11.1429 6.72219C11.2536 6.7301 11.3874 6.64407 11.6551 6.472Z" stroke="#155EEF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,39 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "17",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 17 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Left Icon"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector",
|
||||
"d": "M7.83333 2.66683H5.7C4.5799 2.66683 4.01984 2.66683 3.59202 2.88482C3.21569 3.07656 2.90973 3.38252 2.71799 3.75885C2.5 4.18667 2.5 4.74672 2.5 5.86683V9.3335C2.5 9.95348 2.5 10.2635 2.56815 10.5178C2.75308 11.208 3.29218 11.7471 3.98236 11.932C4.2367 12.0002 4.54669 12.0002 5.16667 12.0002V13.5572C5.16667 13.9124 5.16667 14.09 5.23949 14.1812C5.30282 14.2606 5.39885 14.3067 5.50036 14.3066C5.61708 14.3065 5.75578 14.1955 6.03317 13.9736L7.62348 12.7014C7.94834 12.4415 8.11078 12.3115 8.29166 12.2191C8.45213 12.1371 8.62295 12.0772 8.79948 12.041C8.99845 12.0002 9.20646 12.0002 9.6225 12.0002H10.6333C11.7534 12.0002 12.3135 12.0002 12.7413 11.7822C13.1176 11.5904 13.4236 11.2845 13.6153 10.9081C13.8333 10.4803 13.8333 9.92027 13.8333 8.80016V8.66683M11.6551 6.472L14.8021 4.44889C15.0344 4.29958 15.1505 4.22493 15.1906 4.13C15.2257 4.04706 15.2257 3.95347 15.1906 3.87052C15.1505 3.7756 15.0344 3.70094 14.8021 3.55163L11.6551 1.52852C11.3874 1.35646 11.2536 1.27043 11.1429 1.27833C11.0465 1.28522 10.9578 1.33365 10.8998 1.41105C10.8333 1.49987 10.8333 1.65896 10.8333 1.97715V6.02337C10.8333 6.34156 10.8333 6.50066 10.8998 6.58948C10.9578 6.66688 11.0465 6.71531 11.1429 6.72219C11.2536 6.7301 11.3874 6.64407 11.6551 6.472Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "1.5",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "MessagePlay"
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './MessagePlay.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 = 'MessagePlay'
|
||||
|
||||
export default Icon
|
@ -4,3 +4,4 @@ export { default as ChatBot } from './ChatBot'
|
||||
export { default as CuteRobot } from './CuteRobot'
|
||||
export { default as MessageCheckRemove } from './MessageCheckRemove'
|
||||
export { default as MessageFastPlus } from './MessageFastPlus'
|
||||
export { default as MessagePlay } from './MessagePlay'
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
useEdges,
|
||||
useNodes,
|
||||
} from 'reactflow'
|
||||
import cn from 'classnames'
|
||||
import BlockIcon from '../block-icon'
|
||||
import {
|
||||
useChecklist,
|
||||
@ -28,7 +29,12 @@ import {
|
||||
} from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
|
||||
|
||||
const WorkflowChecklist = () => {
|
||||
type WorkflowChecklistProps = {
|
||||
disabled: boolean
|
||||
}
|
||||
const WorkflowChecklist = ({
|
||||
disabled,
|
||||
}: WorkflowChecklistProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
@ -46,8 +52,13 @@ const WorkflowChecklist = () => {
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<div className='relative flex items-center justify-center p-0.5 w-8 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs'>
|
||||
<PortalToFollowElemTrigger onClick={() => !disabled && setOpen(v => !v)}>
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex items-center justify-center p-0.5 w-8 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs',
|
||||
disabled && 'opacity-50 cursor-not-allowed',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
group flex items-center justify-center w-full h-full rounded-md cursor-pointer
|
||||
|
@ -2,7 +2,6 @@ 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 = () => {
|
||||
@ -13,12 +12,9 @@ const EditingTitle = () => {
|
||||
|
||||
return (
|
||||
<div className='flex items-center h-[18px] text-xs text-gray-500'>
|
||||
<Edit03 className='mr-1 w-3 h-3 text-gray-400' />
|
||||
{t('workflow.common.editing')}
|
||||
{
|
||||
!!draftUpdatedAt && (
|
||||
<>
|
||||
<span className='flex items-center mx-1'>·</span>
|
||||
{t('workflow.common.autoSaved')} {dayjs(draftUpdatedAt).format('HH:mm:ss')}
|
||||
</>
|
||||
)
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
useChecklistBeforePublish,
|
||||
useNodesReadOnly,
|
||||
useNodesSyncDraft,
|
||||
useWorkflowMode,
|
||||
useWorkflowRun,
|
||||
} from '../hooks'
|
||||
import AppPublisher from '../../app/app-publisher'
|
||||
@ -21,12 +22,13 @@ import RunAndHistory from './run-and-history'
|
||||
import EditingTitle from './editing-title'
|
||||
import RunningTitle from './running-title'
|
||||
import RestoringTitle from './restoring-title'
|
||||
import ViewHistory from './view-history'
|
||||
import Checklist from './checklist'
|
||||
import { Grid01 } from '@/app/components/base/icons/src/vender/line/layout'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { publishWorkflow } from '@/service/workflow'
|
||||
import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
|
||||
const Header: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -38,18 +40,21 @@ const Header: FC = () => {
|
||||
nodesReadOnly,
|
||||
getNodesReadOnly,
|
||||
} = useNodesReadOnly()
|
||||
const isRestoring = useStore(s => s.isRestoring)
|
||||
const publishedAt = useStore(s => s.publishedAt)
|
||||
const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
|
||||
const {
|
||||
handleLoadBackupDraft,
|
||||
handleRunSetting,
|
||||
handleBackupDraft,
|
||||
handleRestoreFromPublishedWorkflow,
|
||||
} = useWorkflowRun()
|
||||
const { handleCheckBeforePublish } = useChecklistBeforePublish()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const {
|
||||
normal,
|
||||
restoring,
|
||||
viewHistory,
|
||||
} = useWorkflowMode()
|
||||
|
||||
const handleShowFeatures = useCallback(() => {
|
||||
const {
|
||||
@ -62,10 +67,6 @@ const Header: FC = () => {
|
||||
setShowFeaturesPanel(true)
|
||||
}, [workflowStore, getNodesReadOnly])
|
||||
|
||||
const handleGoBackToEdit = useCallback(() => {
|
||||
handleRunSetting(true)
|
||||
}, [handleRunSetting])
|
||||
|
||||
const handleCancelRestore = useCallback(() => {
|
||||
handleLoadBackupDraft()
|
||||
workflowStore.setState({ isRestoring: false })
|
||||
@ -102,6 +103,11 @@ const Header: FC = () => {
|
||||
handleSyncWorkflowDraft(true)
|
||||
}, [handleSyncWorkflowDraft])
|
||||
|
||||
const handleGoBackToEdit = useCallback(() => {
|
||||
handleLoadBackupDraft()
|
||||
workflowStore.setState({ historyWorkflowData: undefined })
|
||||
}, [workflowStore, handleLoadBackupDraft])
|
||||
|
||||
return (
|
||||
<div
|
||||
className='absolute top-0 left-0 z-10 flex items-center justify-between w-full px-3 h-14'
|
||||
@ -116,39 +122,25 @@ const Header: FC = () => {
|
||||
)
|
||||
}
|
||||
{
|
||||
!nodesReadOnly && !isRestoring && <EditingTitle />
|
||||
normal && <EditingTitle />
|
||||
}
|
||||
{
|
||||
nodesReadOnly && !isRestoring && <RunningTitle />
|
||||
viewHistory && <RunningTitle />
|
||||
}
|
||||
{
|
||||
isRestoring && <RestoringTitle />
|
||||
restoring && <RestoringTitle />
|
||||
}
|
||||
</div>
|
||||
{
|
||||
!isRestoring && (
|
||||
normal && (
|
||||
<div className='flex items-center'>
|
||||
{
|
||||
nodesReadOnly && (
|
||||
<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='w-4 h-4 mr-1' />
|
||||
{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-gray-700
|
||||
border-[0.5px] border-gray-200 shadow-xs
|
||||
${nodesReadOnly && !isRestoring && 'opacity-50 !cursor-not-allowed'}
|
||||
${nodesReadOnly && 'opacity-50 !cursor-not-allowed'}
|
||||
`}
|
||||
onClick={handleShowFeatures}
|
||||
>
|
||||
@ -166,19 +158,32 @@ const Header: FC = () => {
|
||||
crossAxisOffset: 53,
|
||||
}}
|
||||
/>
|
||||
{
|
||||
!nodesReadOnly && (
|
||||
<>
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Checklist />
|
||||
</>
|
||||
)
|
||||
}
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Checklist disabled={nodesReadOnly} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
isRestoring && (
|
||||
viewHistory && (
|
||||
<div className='flex items-center'>
|
||||
<ViewHistory withText />
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Button
|
||||
type='primary'
|
||||
className={`
|
||||
mr-2 px-3 py-0 h-8 text-[13px] font-medium
|
||||
border-[0.5px] border-gray-200 shadow-xs
|
||||
`}
|
||||
onClick={handleGoBackToEdit}
|
||||
>
|
||||
<ArrowNarrowLeft className='w-4 h-4 mr-1' />
|
||||
{t('workflow.common.goBackToEdit')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
restoring && (
|
||||
<div className='flex items-center'>
|
||||
<Button
|
||||
className={`
|
||||
|
@ -2,14 +2,15 @@ import type { FC } from 'react'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import cn from 'classnames'
|
||||
import {
|
||||
useStore,
|
||||
useWorkflowStore,
|
||||
} from '../store'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodesReadOnly,
|
||||
useNodesSyncDraft,
|
||||
useWorkflowInteractions,
|
||||
useWorkflowRun,
|
||||
} from '../hooks'
|
||||
import {
|
||||
@ -23,6 +24,7 @@ import {
|
||||
} from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
import { Loading02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import { MessagePlay } from '@/app/components/base/icons/src/vender/line/communication'
|
||||
|
||||
const RunMode = memo(() => {
|
||||
const { t } = useTranslation()
|
||||
@ -31,15 +33,12 @@ const RunMode = memo(() => {
|
||||
const featuresStore = useFeaturesStore()
|
||||
const {
|
||||
handleStopRun,
|
||||
handleRunSetting,
|
||||
handleRun,
|
||||
} = useWorkflowRun()
|
||||
const {
|
||||
doSyncWorkflowDraft,
|
||||
handleSyncWorkflowDraft,
|
||||
} = useNodesSyncDraft()
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const showInputsPanel = useStore(s => s.showInputsPanel)
|
||||
const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
@ -55,23 +54,23 @@ const RunMode = memo(() => {
|
||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
const startVariables = startNode?.data.variables || []
|
||||
const fileSettings = featuresStore!.getState().features.file
|
||||
const {
|
||||
setShowDebugAndPreviewPanel,
|
||||
setShowInputsPanel,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (!startVariables.length && !fileSettings?.image?.enabled) {
|
||||
await doSyncWorkflowDraft()
|
||||
handleRunSetting()
|
||||
handleRun({ inputs: {}, files: [] })
|
||||
setShowDebugAndPreviewPanel(true)
|
||||
setShowInputsPanel(false)
|
||||
}
|
||||
else {
|
||||
workflowStore.setState({
|
||||
historyWorkflowData: undefined,
|
||||
showInputsPanel: true,
|
||||
})
|
||||
handleSyncWorkflowDraft(true)
|
||||
setShowDebugAndPreviewPanel(true)
|
||||
setShowInputsPanel(true)
|
||||
}
|
||||
}, [
|
||||
workflowStore,
|
||||
handleSyncWorkflowDraft,
|
||||
handleRunSetting,
|
||||
handleRun,
|
||||
doSyncWorkflowDraft,
|
||||
store,
|
||||
@ -81,12 +80,11 @@ const RunMode = memo(() => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600
|
||||
hover:bg-primary-50 cursor-pointer
|
||||
${showInputsPanel && 'bg-primary-50'}
|
||||
${isRunning && 'bg-primary-50 !cursor-not-allowed'}
|
||||
`}
|
||||
className={cn(
|
||||
'flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600',
|
||||
'hover:bg-primary-50 cursor-pointer',
|
||||
isRunning && 'bg-primary-50 !cursor-not-allowed',
|
||||
)}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{
|
||||
@ -122,38 +120,34 @@ RunMode.displayName = 'RunMode'
|
||||
|
||||
const PreviewMode = memo(() => {
|
||||
const { t } = useTranslation()
|
||||
const { handleRunSetting } = useWorkflowRun()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
|
||||
|
||||
const handleClick = () => {
|
||||
handleSyncWorkflowDraft(true)
|
||||
handleRunSetting()
|
||||
const {
|
||||
showDebugAndPreviewPanel,
|
||||
setShowDebugAndPreviewPanel,
|
||||
setHistoryWorkflowData,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (showDebugAndPreviewPanel)
|
||||
handleCancelDebugAndPreviewPanel()
|
||||
else
|
||||
setShowDebugAndPreviewPanel(true)
|
||||
|
||||
setHistoryWorkflowData(undefined)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600
|
||||
hover:bg-primary-50 cursor-pointer
|
||||
${nodesReadOnly && 'bg-primary-50 opacity-50 !cursor-not-allowed'}
|
||||
`}
|
||||
onClick={() => !nodesReadOnly && handleClick()}
|
||||
className={cn(
|
||||
'flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600',
|
||||
'hover:bg-primary-50 cursor-pointer',
|
||||
)}
|
||||
onClick={() => handleClick()}
|
||||
>
|
||||
{
|
||||
nodesReadOnly
|
||||
? (
|
||||
<>
|
||||
{t('workflow.common.inPreview')}
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<Play className='mr-1 w-4 h-4' />
|
||||
{t('workflow.common.preview')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
<MessagePlay className='mr-1 w-4 h-4' />
|
||||
{t('workflow.common.debugAndPreview')}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
@ -1,22 +1,22 @@
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { Play } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
|
||||
import { useIsChatMode } from '../hooks'
|
||||
import { useStore } from '../store'
|
||||
import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time'
|
||||
|
||||
const RunningTitle = () => {
|
||||
const { t } = useTranslation()
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const isChatMode = useIsChatMode()
|
||||
const historyWorkflowData = useStore(s => s.historyWorkflowData)
|
||||
|
||||
return (
|
||||
<div className='flex items-center h-[18px] text-xs text-primary-600'>
|
||||
<Play className='mr-1 w-3 h-3' />
|
||||
{
|
||||
appDetail?.mode === 'advanced-chat'
|
||||
? t('workflow.common.inPreviewMode')
|
||||
: t('workflow.common.inRunMode')
|
||||
}
|
||||
<div className='flex items-center h-[18px] text-xs text-gray-500'>
|
||||
<ClockPlay className='mr-1 w-3 h-3 text-gray-500' />
|
||||
<span>{isChatMode ? `Test Chat#${historyWorkflowData?.sequence_number}` : `Test Run#${historyWorkflowData?.sequence_number}`}</span>
|
||||
<span className='mx-1'>·</span>
|
||||
<span className='text-gray-500'>Test Run#2</span>
|
||||
<span className='ml-1 uppercase flex items-center px-1 h-[18px] rounded-[5px] border border-indigo-300 bg-white/[0.48] text-[10px] font-semibold text-indigo-600'>
|
||||
{t('workflow.common.viewOnly')}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -8,7 +8,9 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodesInteractions,
|
||||
useWorkflow,
|
||||
useWorkflowInteractions,
|
||||
useWorkflowRun,
|
||||
} from '../hooks'
|
||||
import { WorkflowRunningStatus } from '../types'
|
||||
@ -35,11 +37,22 @@ import {
|
||||
useWorkflowStore,
|
||||
} from '@/app/components/workflow/store'
|
||||
|
||||
const ViewHistory = () => {
|
||||
type ViewHistoryProps = {
|
||||
withText?: boolean
|
||||
}
|
||||
const ViewHistory = ({
|
||||
withText,
|
||||
}: ViewHistoryProps) => {
|
||||
const { t } = useTranslation()
|
||||
const isChatMode = useIsChatMode()
|
||||
const [open, setOpen] = useState(false)
|
||||
const { formatTimeFromNow } = useWorkflow()
|
||||
const {
|
||||
handleNodesCancelSelected,
|
||||
} = useNodesInteractions()
|
||||
const {
|
||||
handleCancelDebugAndPreviewPanel,
|
||||
} = useWorkflowInteractions()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({
|
||||
appDetail: state.appDetail,
|
||||
@ -57,31 +70,49 @@ const ViewHistory = () => {
|
||||
return (
|
||||
(
|
||||
<PortalToFollowElem
|
||||
placement='bottom-end'
|
||||
placement={withText ? 'bottom-start' : 'bottom-end'}
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 131,
|
||||
crossAxis: withText ? -8 : 10,
|
||||
}}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<TooltipPlus
|
||||
popupContent={t('workflow.common.viewRunHistory')}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex items-center justify-center w-7 h-7 rounded-md hover:bg-black/5 cursor-pointer
|
||||
${open && 'bg-primary-50'}
|
||||
`}
|
||||
onClick={() => {
|
||||
setCurrentLogItem()
|
||||
setShowMessageLogModal(false)
|
||||
}}
|
||||
>
|
||||
<ClockPlay className={`w-4 h-4 ${open ? 'text-primary-600' : 'text-gray-500'}`} />
|
||||
</div>
|
||||
</TooltipPlus>
|
||||
{
|
||||
withText && (
|
||||
<div className={cn(
|
||||
'flex items-center px-3 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs',
|
||||
'text-[13px] font-medium text-primary-600 cursor-pointer',
|
||||
open && '!bg-primary-50',
|
||||
)}>
|
||||
<ClockPlay
|
||||
className={'mr-1 w-4 h-4'}
|
||||
/>
|
||||
{t('workflow.common.showRunHistory')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!withText && (
|
||||
<TooltipPlus
|
||||
popupContent={t('workflow.common.viewRunHistory')}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex items-center justify-center w-7 h-7 rounded-md hover:bg-black/5 cursor-pointer
|
||||
${open && 'bg-primary-50'}
|
||||
`}
|
||||
onClick={() => {
|
||||
setCurrentLogItem()
|
||||
setShowMessageLogModal(false)
|
||||
}}
|
||||
>
|
||||
<ClockPlay className={`w-4 h-4 ${open ? 'text-primary-600' : 'text-gray-500'}`} />
|
||||
</div>
|
||||
</TooltipPlus>
|
||||
)
|
||||
}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[12]'>
|
||||
<div
|
||||
@ -138,6 +169,8 @@ const ViewHistory = () => {
|
||||
})
|
||||
handleBackupDraft()
|
||||
setOpen(false)
|
||||
handleNodesCancelSelected()
|
||||
handleCancelDebugAndPreviewPanel()
|
||||
}}
|
||||
>
|
||||
{
|
||||
|
@ -7,3 +7,5 @@ export * from './use-workflow'
|
||||
export * from './use-workflow-run'
|
||||
export * from './use-workflow-template'
|
||||
export * from './use-checklist'
|
||||
export * from './use-workflow-mode'
|
||||
export * from './use-workflow-interactions'
|
||||
|
@ -201,6 +201,20 @@ export const useEdgesInteractions = () => {
|
||||
setEdges(newEdges)
|
||||
}, [store])
|
||||
|
||||
const handleEdgeCancelRunningStatus = useCallback(() => {
|
||||
const {
|
||||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
draft.forEach((edge) => {
|
||||
edge.data._runned = false
|
||||
})
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}, [store])
|
||||
|
||||
return {
|
||||
handleEdgeEnter,
|
||||
handleEdgeLeave,
|
||||
@ -208,5 +222,6 @@ export const useEdgesInteractions = () => {
|
||||
handleEdgeDelete,
|
||||
handleEdgesChange,
|
||||
handleVariableAssignerEdgesChange,
|
||||
handleEdgeCancelRunningStatus,
|
||||
}
|
||||
}
|
||||
|
@ -243,9 +243,6 @@ export const useNodesInteractions = () => {
|
||||
}, [store, getNodesReadOnly])
|
||||
|
||||
const handleNodeSelect = useCallback((nodeId: string, cancelSelection?: boolean) => {
|
||||
if (getNodesReadOnly() && !workflowStore.getState().isRestoring)
|
||||
return
|
||||
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
@ -289,14 +286,11 @@ export const useNodesInteractions = () => {
|
||||
setEdges(newEdges)
|
||||
|
||||
handleSyncWorkflowDraft()
|
||||
}, [store, handleSyncWorkflowDraft, getNodesReadOnly, workflowStore])
|
||||
}, [store, handleSyncWorkflowDraft])
|
||||
|
||||
const handleNodeClick = useCallback<NodeMouseHandler>((_, node) => {
|
||||
if (getNodesReadOnly() && !workflowStore.getState().isRestoring)
|
||||
return
|
||||
|
||||
handleNodeSelect(node.id)
|
||||
}, [handleNodeSelect, getNodesReadOnly, workflowStore])
|
||||
}, [handleNodeSelect])
|
||||
|
||||
const handleNodeConnect = useCallback<OnConnect>(({
|
||||
source,
|
||||
@ -834,6 +828,36 @@ export const useNodesInteractions = () => {
|
||||
handleNodeDelete(node.id)
|
||||
}, [getNodesReadOnly, handleNodeDelete, store, workflowStore])
|
||||
|
||||
const handleNodeCancelRunningStatus = useCallback(() => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
|
||||
const nodes = getNodes()
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.data._runningStatus = undefined
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}, [store])
|
||||
|
||||
const handleNodesCancelSelected = useCallback(() => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
|
||||
const nodes = getNodes()
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.data.selected = false
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}, [store])
|
||||
|
||||
return {
|
||||
handleNodeDragStart,
|
||||
handleNodeDrag,
|
||||
@ -853,5 +877,7 @@ export const useNodesInteractions = () => {
|
||||
handleNodeCut,
|
||||
handleNodeDeleteSelected,
|
||||
handleNodePaste,
|
||||
handleNodeCancelRunningStatus,
|
||||
handleNodesCancelSelected,
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,8 @@ export const useNodesSyncDraft = () => {
|
||||
}, [store, featuresStore, workflowStore])
|
||||
|
||||
const syncWorkflowDraftWhenPageClose = useCallback(() => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
const postParams = getPostParams()
|
||||
|
||||
if (postParams) {
|
||||
@ -89,16 +91,18 @@ export const useNodesSyncDraft = () => {
|
||||
JSON.stringify(postParams.params),
|
||||
)
|
||||
}
|
||||
}, [getPostParams, params.appId])
|
||||
}, [getPostParams, params.appId, getNodesReadOnly])
|
||||
|
||||
const doSyncWorkflowDraft = useCallback(async (appId?: string) => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
const postParams = getPostParams(appId)
|
||||
|
||||
if (postParams) {
|
||||
const res = await syncWorkflowDraft(postParams)
|
||||
workflowStore.getState().setDraftUpdatedAt(res.updated_at)
|
||||
}
|
||||
}, [workflowStore, getPostParams])
|
||||
}, [workflowStore, getPostParams, getNodesReadOnly])
|
||||
|
||||
const handleSyncWorkflowDraft = useCallback((sync?: boolean, appId?: string) => {
|
||||
if (getNodesReadOnly())
|
||||
|
@ -0,0 +1,50 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useReactFlow } from 'reactflow'
|
||||
import { useWorkflowStore } from '../store'
|
||||
import { WORKFLOW_DATA_UPDATE } from '../constants'
|
||||
import type { WorkflowDataUpdator } from '../types'
|
||||
import {
|
||||
initialEdges,
|
||||
initialNodes,
|
||||
} from '../utils'
|
||||
import { useEdgesInteractions } from './use-edges-interactions'
|
||||
import { useNodesInteractions } from './use-nodes-interactions'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
|
||||
export const useWorkflowInteractions = () => {
|
||||
const reactflow = useReactFlow()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { handleNodeCancelRunningStatus } = useNodesInteractions()
|
||||
const { handleEdgeCancelRunningStatus } = useEdgesInteractions()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
||||
const handleCancelDebugAndPreviewPanel = useCallback(() => {
|
||||
workflowStore.setState({
|
||||
showDebugAndPreviewPanel: false,
|
||||
})
|
||||
handleNodeCancelRunningStatus()
|
||||
handleEdgeCancelRunningStatus()
|
||||
}, [workflowStore, handleNodeCancelRunningStatus, handleEdgeCancelRunningStatus])
|
||||
|
||||
const handleUpdateWorkflowCanvas = useCallback((payload: WorkflowDataUpdator) => {
|
||||
const {
|
||||
nodes,
|
||||
edges,
|
||||
viewport,
|
||||
} = payload
|
||||
const { setViewport } = reactflow
|
||||
eventEmitter?.emit({
|
||||
type: WORKFLOW_DATA_UPDATE,
|
||||
payload: {
|
||||
nodes: initialNodes(nodes, edges),
|
||||
edges: initialEdges(edges, nodes),
|
||||
},
|
||||
} as any)
|
||||
setViewport(viewport)
|
||||
}, [eventEmitter, reactflow])
|
||||
|
||||
return {
|
||||
handleCancelDebugAndPreviewPanel,
|
||||
handleUpdateWorkflowCanvas,
|
||||
}
|
||||
}
|
14
web/app/components/workflow/hooks/use-workflow-mode.ts
Normal file
14
web/app/components/workflow/hooks/use-workflow-mode.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useStore } from '../store'
|
||||
|
||||
export const useWorkflowMode = () => {
|
||||
const historyWorkflowData = useStore(s => s.historyWorkflowData)
|
||||
const isRestoring = useStore(s => s.isRestoring)
|
||||
return useMemo(() => {
|
||||
return {
|
||||
normal: !historyWorkflowData && !isRestoring,
|
||||
restoring: isRestoring,
|
||||
viewHistory: !!historyWorkflowData,
|
||||
}
|
||||
}, [historyWorkflowData, isRestoring])
|
||||
}
|
@ -5,11 +5,12 @@ import {
|
||||
} from 'reactflow'
|
||||
import produce from 'immer'
|
||||
import { useWorkflowStore } from '../store'
|
||||
import { useNodesSyncDraft } from '../hooks'
|
||||
import {
|
||||
NodeRunningStatus,
|
||||
WorkflowRunningStatus,
|
||||
} from '../types'
|
||||
import { useWorkflow } from './use-workflow'
|
||||
import { useWorkflowInteractions } from './use-workflow-interactions'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import type { IOtherOptions } from '@/service/base'
|
||||
import { ssePost } from '@/service/base'
|
||||
@ -24,7 +25,8 @@ export const useWorkflowRun = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const reactflow = useReactFlow()
|
||||
const featuresStore = useFeaturesStore()
|
||||
const { renderTreeFromRecord } = useWorkflow()
|
||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { handleUpdateWorkflowCanvas } = useWorkflowInteractions()
|
||||
|
||||
const handleBackupDraft = useCallback(() => {
|
||||
const {
|
||||
@ -45,15 +47,11 @@ export const useWorkflowRun = () => {
|
||||
viewport: getViewport(),
|
||||
features,
|
||||
})
|
||||
doSyncWorkflowDraft()
|
||||
}
|
||||
}, [reactflow, workflowStore, store, featuresStore])
|
||||
}, [reactflow, workflowStore, store, featuresStore, doSyncWorkflowDraft])
|
||||
|
||||
const handleLoadBackupDraft = useCallback(() => {
|
||||
const {
|
||||
setNodes,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
const { setViewport } = reactflow
|
||||
const {
|
||||
backupDraft,
|
||||
setBackupDraft,
|
||||
@ -66,64 +64,32 @@ export const useWorkflowRun = () => {
|
||||
viewport,
|
||||
features,
|
||||
} = backupDraft
|
||||
setNodes(nodes)
|
||||
setEdges(edges)
|
||||
setViewport(viewport)
|
||||
handleUpdateWorkflowCanvas({
|
||||
nodes,
|
||||
edges,
|
||||
viewport,
|
||||
})
|
||||
featuresStore!.setState({ features })
|
||||
setBackupDraft(undefined)
|
||||
}
|
||||
}, [store, reactflow, workflowStore, featuresStore])
|
||||
}, [handleUpdateWorkflowCanvas, workflowStore, featuresStore])
|
||||
|
||||
const handleRunSetting = useCallback((shouldClear?: boolean) => {
|
||||
if (shouldClear) {
|
||||
workflowStore.setState({
|
||||
workflowRunningData: undefined,
|
||||
historyWorkflowData: undefined,
|
||||
showInputsPanel: false,
|
||||
})
|
||||
}
|
||||
else {
|
||||
workflowStore.setState({
|
||||
workflowRunningData: {
|
||||
result: {
|
||||
status: shouldClear ? '' : WorkflowRunningStatus.Waiting,
|
||||
},
|
||||
tracing: [],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
setNodes,
|
||||
getNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
|
||||
if (shouldClear) {
|
||||
handleLoadBackupDraft()
|
||||
}
|
||||
else {
|
||||
handleBackupDraft()
|
||||
const newNodes = produce(getNodes(), (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.data._runningStatus = NodeRunningStatus.Waiting
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
draft.forEach((edge) => {
|
||||
edge.data._runned = false
|
||||
})
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}
|
||||
}, [store, handleLoadBackupDraft, handleBackupDraft, workflowStore])
|
||||
|
||||
const handleRun = useCallback((
|
||||
const handleRun = useCallback(async (
|
||||
params: any,
|
||||
callback?: IOtherOptions,
|
||||
) => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
const newNodes = produce(getNodes(), (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.data.selected = false
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
await doSyncWorkflowDraft()
|
||||
|
||||
const {
|
||||
onWorkflowStarted,
|
||||
onWorkflowFinished,
|
||||
@ -151,15 +117,14 @@ export const useWorkflowRun = () => {
|
||||
let prevNodeId = ''
|
||||
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.result = {
|
||||
...draft?.result,
|
||||
setWorkflowRunningData({
|
||||
result: {
|
||||
status: WorkflowRunningStatus.Running,
|
||||
}
|
||||
}))
|
||||
},
|
||||
tracing: [],
|
||||
})
|
||||
|
||||
ssePost(
|
||||
url,
|
||||
@ -174,8 +139,6 @@ export const useWorkflowRun = () => {
|
||||
setWorkflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
@ -188,12 +151,6 @@ export const useWorkflowRun = () => {
|
||||
}
|
||||
}))
|
||||
|
||||
const newNodes = produce(getNodes(), (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.data._runningStatus = NodeRunningStatus.Waiting
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
draft.forEach((edge) => {
|
||||
edge.data = {
|
||||
@ -253,6 +210,7 @@ export const useWorkflowRun = () => {
|
||||
setNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
transform,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
@ -268,12 +226,12 @@ export const useWorkflowRun = () => {
|
||||
const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id)
|
||||
const currentNode = nodes[currentNodeIndex]
|
||||
const position = currentNode.position
|
||||
const zoom = 1
|
||||
const zoom = transform[2]
|
||||
|
||||
setViewport({
|
||||
x: (clientWidth - 400 - currentNode.width!) / 2 - position.x,
|
||||
y: (clientHeight - currentNode.height!) / 2 - position.y,
|
||||
zoom,
|
||||
x: (clientWidth - 400 - currentNode.width! * zoom) / 2 - position.x * zoom,
|
||||
y: (clientHeight - currentNode.height! * zoom) / 2 - position.y * zoom,
|
||||
zoom: transform[2],
|
||||
})
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
|
||||
@ -329,7 +287,7 @@ export const useWorkflowRun = () => {
|
||||
...restCallback,
|
||||
},
|
||||
)
|
||||
}, [store, reactflow, workflowStore])
|
||||
}, [store, reactflow, workflowStore, doSyncWorkflowDraft])
|
||||
|
||||
const handleStopRun = useCallback((taskId: string) => {
|
||||
const appId = useAppStore.getState().appDetail?.id
|
||||
@ -344,18 +302,21 @@ export const useWorkflowRun = () => {
|
||||
if (publishedWorkflow) {
|
||||
const nodes = publishedWorkflow.graph.nodes
|
||||
const edges = publishedWorkflow.graph.edges
|
||||
const viewport = publishedWorkflow.graph.viewport
|
||||
const viewport = publishedWorkflow.graph.viewport!
|
||||
|
||||
renderTreeFromRecord(nodes, edges, viewport)
|
||||
handleUpdateWorkflowCanvas({
|
||||
nodes,
|
||||
edges,
|
||||
viewport,
|
||||
})
|
||||
featuresStore?.setState({ features: publishedWorkflow.features })
|
||||
workflowStore.getState().setPublishedAt(publishedWorkflow.created_at)
|
||||
}
|
||||
}, [featuresStore, workflowStore, renderTreeFromRecord])
|
||||
}, [featuresStore, handleUpdateWorkflowCanvas, workflowStore])
|
||||
|
||||
return {
|
||||
handleBackupDraft,
|
||||
handleLoadBackupDraft,
|
||||
handleRunSetting,
|
||||
handleRun,
|
||||
handleStopRun,
|
||||
handleRestoreFromPublishedWorkflow,
|
||||
|
@ -16,15 +16,11 @@ import {
|
||||
} from 'reactflow'
|
||||
import type {
|
||||
Connection,
|
||||
Viewport,
|
||||
} from 'reactflow'
|
||||
import {
|
||||
getLayoutByDagre,
|
||||
initialEdges,
|
||||
initialNodes,
|
||||
} from '../utils'
|
||||
import type {
|
||||
Edge,
|
||||
Node,
|
||||
ValueSelector,
|
||||
} from '../types'
|
||||
@ -39,7 +35,6 @@ import {
|
||||
import {
|
||||
AUTO_LAYOUT_OFFSET,
|
||||
SUPPORT_OUTPUT_VARS_NODE,
|
||||
WORKFLOW_DATA_UPDATE,
|
||||
} from '../constants'
|
||||
import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
|
||||
import { useNodesExtraData } from './use-nodes-data'
|
||||
@ -58,7 +53,6 @@ import {
|
||||
fetchAllCustomTools,
|
||||
} from '@/service/tools'
|
||||
import I18n from '@/context/i18n'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
|
||||
export const useIsChatMode = () => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
@ -73,7 +67,6 @@ export const useWorkflow = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
||||
const setPanelWidth = useCallback((width: number) => {
|
||||
localStorage.setItem('workflow-node-panel-width', `${width}`)
|
||||
@ -323,23 +316,6 @@ export const useWorkflow = () => {
|
||||
return dayjs(time).locale(locale === 'zh-Hans' ? 'zh-cn' : locale).fromNow()
|
||||
}, [locale])
|
||||
|
||||
const renderTreeFromRecord = useCallback((nodes: Node[], edges: Edge[], viewport?: Viewport) => {
|
||||
const { setViewport } = reactflow
|
||||
|
||||
const nodesMap = nodes.map(node => ({ ...node, data: { ...node.data, selected: false } }))
|
||||
|
||||
eventEmitter?.emit({
|
||||
type: WORKFLOW_DATA_UPDATE,
|
||||
payload: {
|
||||
nodes: initialNodes(nodesMap, edges),
|
||||
edges: initialEdges(edges, nodesMap),
|
||||
},
|
||||
} as any)
|
||||
|
||||
if (viewport)
|
||||
setViewport(viewport)
|
||||
}, [reactflow, eventEmitter])
|
||||
|
||||
const getNode = useCallback((nodeId?: string) => {
|
||||
const { getNodes } = store.getState()
|
||||
const nodes = getNodes()
|
||||
@ -369,7 +345,6 @@ export const useWorkflow = () => {
|
||||
isNodeVarsUsedInNodes,
|
||||
isValidConnection,
|
||||
formatTimeFromNow,
|
||||
renderTreeFromRecord,
|
||||
getNode,
|
||||
getBeforeNodeById,
|
||||
enableShortcuts,
|
||||
@ -510,11 +485,11 @@ export const useNodesReadOnly = () => {
|
||||
isRestoring,
|
||||
} = workflowStore.getState()
|
||||
|
||||
return workflowRunningData || historyWorkflowData || isRestoring
|
||||
return workflowRunningData?.result.status === WorkflowRunningStatus.Running || historyWorkflowData || isRestoring
|
||||
}, [workflowStore])
|
||||
|
||||
return {
|
||||
nodesReadOnly: !!(workflowRunningData || historyWorkflowData || isRestoring),
|
||||
nodesReadOnly: !!(workflowRunningData?.result.status === WorkflowRunningStatus.Running || historyWorkflowData || isRestoring),
|
||||
getNodesReadOnly,
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ type Props = {
|
||||
value: any
|
||||
onChange: (value: any) => void
|
||||
className?: string
|
||||
autoFocus?: boolean
|
||||
}
|
||||
|
||||
const FormItem: FC<Props> = ({
|
||||
@ -28,6 +29,7 @@ const FormItem: FC<Props> = ({
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
autoFocus,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { type } = payload
|
||||
@ -87,6 +89,7 @@ const FormItem: FC<Props> = ({
|
||||
value={value || ''}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
placeholder={t('appDebug.variableConig.inputPlaceholder')!}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -99,6 +102,7 @@ const FormItem: FC<Props> = ({
|
||||
value={value || ''}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
placeholder={t('appDebug.variableConig.inputPlaceholder')!}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -110,6 +114,7 @@ const FormItem: FC<Props> = ({
|
||||
value={value || ''}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
placeholder={t('appDebug.variableConig.inputPlaceholder')!}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -141,9 +146,9 @@ const FormItem: FC<Props> = ({
|
||||
type === InputVarType.files && (
|
||||
<TextGenerationImageUploader
|
||||
settings={{
|
||||
...fileSettings.image,
|
||||
...fileSettings?.image,
|
||||
detail: Resolution.high,
|
||||
}}
|
||||
} as any}
|
||||
onFilesChange={files => onChange(files.filter(file => file.progress !== -1).map(fileItem => ({
|
||||
type: 'image',
|
||||
transfer_method: fileItem.type,
|
||||
|
@ -5,6 +5,7 @@ import type {
|
||||
import {
|
||||
cloneElement,
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import type { NodeProps } from '../../types'
|
||||
import {
|
||||
@ -38,11 +39,24 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
}) => {
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const toolIcon = useToolIcon(data)
|
||||
|
||||
const {
|
||||
showRunningBorder,
|
||||
showSuccessBorder,
|
||||
showFailedBorder,
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
showRunningBorder: data._runningStatus === NodeRunningStatus.Running && !data.selected,
|
||||
showSuccessBorder: data._runningStatus === NodeRunningStatus.Succeeded && !data.selected,
|
||||
showFailedBorder: data._runningStatus === NodeRunningStatus.Failed && !data.selected,
|
||||
}
|
||||
}, [data._runningStatus, data.selected])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex border-[2px] rounded-2xl
|
||||
${(data.selected && !data._runningStatus && !data._isInvalidConnection) ? 'border-primary-600' : 'border-transparent'}
|
||||
${(data.selected && !data._isInvalidConnection) ? 'border-primary-600' : 'border-transparent'}
|
||||
`}
|
||||
>
|
||||
<div
|
||||
@ -50,15 +64,14 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
group relative pb-1 w-[240px] bg-[#fcfdff] shadow-xs
|
||||
border border-transparent rounded-[15px]
|
||||
${!data._runningStatus && 'hover:shadow-lg'}
|
||||
${data._runningStatus === NodeRunningStatus.Running && '!border-primary-500'}
|
||||
${data._runningStatus === NodeRunningStatus.Succeeded && '!border-[#12B76A]'}
|
||||
${data._runningStatus === NodeRunningStatus.Failed && '!border-[#F04438]'}
|
||||
${data._runningStatus === NodeRunningStatus.Waiting && 'opacity-70'}
|
||||
${showRunningBorder && '!border-primary-500'}
|
||||
${showSuccessBorder && '!border-[#12B76A]'}
|
||||
${showFailedBorder && '!border-[#F04438]'}
|
||||
${data._isInvalidConnection && '!border-[#F04438]'}
|
||||
`}
|
||||
>
|
||||
{
|
||||
data.type !== BlockEnum.VariableAssigner && !data._runningStatus && (
|
||||
data.type !== BlockEnum.VariableAssigner && (
|
||||
<NodeTargetHandle
|
||||
id={id}
|
||||
data={data}
|
||||
@ -68,7 +81,7 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
)
|
||||
}
|
||||
{
|
||||
data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._runningStatus && (
|
||||
data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && (
|
||||
<NodeSourceHandle
|
||||
id={id}
|
||||
data={data}
|
||||
|
@ -7,6 +7,8 @@ import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NextStep from './components/next-step'
|
||||
import PanelOperator from './components/panel-operator'
|
||||
@ -32,6 +34,7 @@ import { canRunBySingle } from '@/app/components/workflow/utils'
|
||||
import { Play } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
|
||||
type BasePanelProps = {
|
||||
children: ReactElement
|
||||
@ -43,6 +46,9 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { showMessageLogModal } = useAppStore(useShallow(state => ({
|
||||
showMessageLogModal: state.showMessageLogModal,
|
||||
})))
|
||||
const panelWidth = localStorage.getItem('workflow-node-panel-width') ? parseFloat(localStorage.getItem('workflow-node-panel-width')!) : 420
|
||||
const {
|
||||
setPanelWidth,
|
||||
@ -82,7 +88,10 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
}, [handleNodeDataUpdateWithSyncDraft, id])
|
||||
|
||||
return (
|
||||
<div className='relative mr-2 h-full'>
|
||||
<div className={cn(
|
||||
'relative mr-2 h-full',
|
||||
showMessageLogModal && '!absolute !mr-0 w-[384px] overflow-hidden -top-[5px] right-[416px] z-0 shadow-lg border-[0.5px] border-gray-200 rounded-2xl transition-all',
|
||||
)}>
|
||||
<div
|
||||
ref={triggerRef}
|
||||
className='absolute top-1/2 -translate-y-1/2 -left-2 w-3 h-6 cursor-col-resize resize-x'>
|
||||
|
@ -5,18 +5,25 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useStore } from '../../store'
|
||||
import {
|
||||
useStore,
|
||||
useWorkflowStore,
|
||||
} from '../../store'
|
||||
import { useWorkflowRun } from '../../hooks'
|
||||
import UserInput from './user-input'
|
||||
import Chat from '@/app/components/base/chat/chat'
|
||||
import type { ChatItem } from '@/app/components/base/chat/types'
|
||||
import { fetchConvesationMessages } from '@/service/debug'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
const ChatRecord = () => {
|
||||
const [fetched, setFetched] = useState(false)
|
||||
const [chatList, setChatList] = useState([])
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { handleLoadBackupDraft } = useWorkflowRun()
|
||||
const historyWorkflowData = useStore(s => s.historyWorkflowData)
|
||||
const currentConversationID = historyWorkflowData?.conversation_id
|
||||
|
||||
@ -79,6 +86,15 @@ const ChatRecord = () => {
|
||||
<>
|
||||
<div className='shrink-0 flex items-center justify-between p-4 pb-1 text-base font-semibold text-gray-900'>
|
||||
{`TEST CHAT#${historyWorkflowData?.sequence_number}`}
|
||||
<div
|
||||
className='flex justify-center items-center w-6 h-6 cursor-pointer'
|
||||
onClick={() => {
|
||||
handleLoadBackupDraft()
|
||||
workflowStore.setState({ historyWorkflowData: undefined })
|
||||
}}
|
||||
>
|
||||
<XClose className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow h-0'>
|
||||
<Chat
|
||||
|
@ -3,10 +3,17 @@ import {
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useKeyPress } from 'ahooks'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
useEdgesInteractions,
|
||||
useNodesInteractions,
|
||||
useWorkflowInteractions,
|
||||
} from '../../hooks'
|
||||
import ChatWrapper from './chat-wrapper'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
export type ChatWrapperRefType = {
|
||||
handleRestart: () => void
|
||||
@ -14,33 +21,56 @@ export type ChatWrapperRefType = {
|
||||
const DebugAndPreview = () => {
|
||||
const { t } = useTranslation()
|
||||
const chatRef = useRef({ handleRestart: () => {} })
|
||||
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
|
||||
const { handleNodeCancelRunningStatus } = useNodesInteractions()
|
||||
const { handleEdgeCancelRunningStatus } = useEdgesInteractions()
|
||||
|
||||
const handleRestartChat = () => {
|
||||
handleNodeCancelRunningStatus()
|
||||
handleEdgeCancelRunningStatus()
|
||||
chatRef.current.handleRestart()
|
||||
}
|
||||
|
||||
useKeyPress('shift.r', () => {
|
||||
chatRef.current.handleRestart()
|
||||
handleRestartChat()
|
||||
}, {
|
||||
exactMatch: true,
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex flex-col w-[400px] rounded-l-2xl h-full border border-black/[0.02] shadow-xl
|
||||
`}
|
||||
className={cn(
|
||||
'flex flex-col w-[400px] rounded-l-2xl h-full border border-black/[0.02]',
|
||||
)}
|
||||
style={{
|
||||
background: 'linear-gradient(156deg, rgba(242, 244, 247, 0.80) 0%, rgba(242, 244, 247, 0.00) 99.43%), var(--white, #FFF)',
|
||||
}}
|
||||
>
|
||||
<div className='shrink-0 flex items-center justify-between px-4 pt-3 pb-2 font-semibold text-gray-900'>
|
||||
<div className='shrink-0 flex items-center justify-between pl-4 pr-3 pt-3 pb-2 font-semibold text-gray-900'>
|
||||
{t('workflow.common.debugAndPreview').toLocaleUpperCase()}
|
||||
<Button
|
||||
className='pl-2.5 pr-[7px] h-8 bg-white border-[0.5px] border-gray-200 shadow-xs rounded-lg text-[13px] text-primary-600 font-semibold'
|
||||
onClick={() => chatRef.current.handleRestart()}
|
||||
>
|
||||
<RefreshCcw01 className='mr-1 w-3.5 h-3.5' />
|
||||
{t('common.operation.refresh')}
|
||||
<div className='ml-2 px-1 leading-[18px] rounded-md border border-gray-200 bg-gray-50 text-[11px] text-gray-500 font-medium'>Shift</div>
|
||||
<div className='ml-0.5 px-1 leading-[18px] rounded-md border border-gray-200 bg-gray-50 text-[11px] text-gray-500 font-medium'>R</div>
|
||||
</Button>
|
||||
<div className='flex items-center'>
|
||||
<Button
|
||||
className='px-2 h-8 bg-white border-[0.5px] border-gray-200 shadow-xs rounded-lg text-xs text-gray-700 font-medium'
|
||||
onClick={() => handleRestartChat()}
|
||||
>
|
||||
<RefreshCcw01 className='shrink-0 mr-1 w-3 h-3 text-gray-500' />
|
||||
<div
|
||||
className='grow truncate uppercase'
|
||||
title={t('common.operation.refresh') || ''}
|
||||
>
|
||||
{t('common.operation.refresh')}
|
||||
</div>
|
||||
<div className='shrink-0 ml-1 px-1 leading-[18px] rounded-md border border-gray-200 bg-gray-50 text-[11px] text-gray-500 font-medium'>Shift</div>
|
||||
<div className='shrink-0 ml-0.5 px-1 leading-[18px] rounded-md border border-gray-200 bg-gray-50 text-[11px] text-gray-500 font-medium'>R</div>
|
||||
</Button>
|
||||
<div className='mx-3 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<div
|
||||
className='flex items-center justify-center w-6 h-6 cursor-pointer'
|
||||
onClick={handleCancelDebugAndPreviewPanel}
|
||||
>
|
||||
<XClose className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow rounded-b-2xl overflow-y-auto'>
|
||||
<ChatWrapper ref={chatRef} />
|
||||
|
@ -56,12 +56,13 @@ const UserInput = () => {
|
||||
expanded && (
|
||||
<div className='py-2 text-[13px] text-gray-900'>
|
||||
{
|
||||
variables.map(variable => (
|
||||
variables.map((variable, index) => (
|
||||
<div
|
||||
key={variable.variable}
|
||||
className='mb-2 last-of-type:mb-0'
|
||||
>
|
||||
<FormItem
|
||||
autoFocus={index === 0}
|
||||
payload={variable}
|
||||
value={inputs[variable.variable]}
|
||||
onChange={v => handleValueChange(variable.variable, v)}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import type { FC } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { memo } from 'react'
|
||||
import { useNodes } from 'reactflow'
|
||||
import cn from 'classnames'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import type { CommonNodeType } from '../types'
|
||||
import { Panel as NodePanel } from '../nodes'
|
||||
@ -23,9 +21,8 @@ const Panel: FC = () => {
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
const isChatMode = useIsChatMode()
|
||||
const selectedNode = nodes.find(node => node.data.selected)
|
||||
const showInputsPanel = useStore(s => s.showInputsPanel)
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const historyWorkflowData = useStore(s => s.historyWorkflowData)
|
||||
const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel)
|
||||
const isRestoring = useStore(s => s.isRestoring)
|
||||
const {
|
||||
enableShortcuts,
|
||||
@ -37,28 +34,13 @@ const Panel: FC = () => {
|
||||
showMessageLogModal: state.showMessageLogModal,
|
||||
setShowMessageLogModal: state.setShowMessageLogModal,
|
||||
})))
|
||||
const {
|
||||
showNodePanel,
|
||||
showDebugAndPreviewPanel,
|
||||
showWorkflowPreview,
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
showNodePanel: !!selectedNode && !workflowRunningData && !historyWorkflowData && !showInputsPanel,
|
||||
showDebugAndPreviewPanel: isChatMode && workflowRunningData && !historyWorkflowData,
|
||||
showWorkflowPreview: !isChatMode && !historyWorkflowData && (workflowRunningData || showInputsPanel),
|
||||
}
|
||||
}, [
|
||||
showInputsPanel,
|
||||
selectedNode,
|
||||
isChatMode,
|
||||
workflowRunningData,
|
||||
historyWorkflowData,
|
||||
])
|
||||
|
||||
return (
|
||||
<div
|
||||
tabIndex={-1}
|
||||
className='absolute top-14 right-0 bottom-2 flex z-10 outline-none'
|
||||
className={cn(
|
||||
'absolute top-14 right-0 bottom-2 flex z-10 outline-none',
|
||||
)}
|
||||
onFocus={disableShortcuts}
|
||||
onBlur={enableShortcuts}
|
||||
key={`${isRestoring}`}
|
||||
@ -76,6 +58,11 @@ const Panel: FC = () => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!selectedNode && (
|
||||
<NodePanel {...selectedNode!} />
|
||||
)
|
||||
}
|
||||
{
|
||||
historyWorkflowData && !isChatMode && (
|
||||
<Record />
|
||||
@ -87,20 +74,15 @@ const Panel: FC = () => {
|
||||
)
|
||||
}
|
||||
{
|
||||
showDebugAndPreviewPanel && (
|
||||
showDebugAndPreviewPanel && isChatMode && (
|
||||
<DebugAndPreview />
|
||||
)
|
||||
}
|
||||
{
|
||||
showWorkflowPreview && (
|
||||
showDebugAndPreviewPanel && !isChatMode && (
|
||||
<WorkflowPreview />
|
||||
)
|
||||
}
|
||||
{
|
||||
showNodePanel && (
|
||||
<NodePanel {...selectedNode!} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ const InputsPanel = ({ onRun }: Props) => {
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const {
|
||||
handleRun,
|
||||
handleRunSetting,
|
||||
} = useWorkflowRun()
|
||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
const startVariables = startNode?.data.variables
|
||||
@ -72,7 +71,6 @@ const InputsPanel = ({ onRun }: Props) => {
|
||||
|
||||
const doRun = () => {
|
||||
onRun()
|
||||
handleRunSetting()
|
||||
handleRun({ inputs, files })
|
||||
}
|
||||
|
||||
@ -87,12 +85,13 @@ const InputsPanel = ({ onRun }: Props) => {
|
||||
<>
|
||||
<div className='px-4 pb-2'>
|
||||
{
|
||||
variables.map(variable => (
|
||||
variables.map((variable, index) => (
|
||||
<div
|
||||
key={variable.variable}
|
||||
className='mb-2 last-of-type:mb-0'
|
||||
>
|
||||
<FormItem
|
||||
autoFocus={index === 0}
|
||||
className='!block'
|
||||
payload={variable}
|
||||
value={inputs[variable.variable]}
|
||||
|
@ -1,16 +1,31 @@
|
||||
import { memo } from 'react'
|
||||
import { memo, useCallback } from 'react'
|
||||
import type { WorkflowDataUpdator } from '../types'
|
||||
import Run from '../run'
|
||||
import { useStore } from '../store'
|
||||
import { useWorkflowInteractions } from '../hooks'
|
||||
|
||||
const Record = () => {
|
||||
const historyWorkflowData = useStore(s => s.historyWorkflowData)
|
||||
const { handleUpdateWorkflowCanvas } = useWorkflowInteractions()
|
||||
|
||||
const handleResultCallback = useCallback((res: any) => {
|
||||
const graph: WorkflowDataUpdator = res.graph
|
||||
handleUpdateWorkflowCanvas({
|
||||
nodes: graph.nodes,
|
||||
edges: graph.edges,
|
||||
viewport: graph.viewport,
|
||||
})
|
||||
}, [handleUpdateWorkflowCanvas])
|
||||
|
||||
return (
|
||||
<div className='flex flex-col w-[400px] h-full rounded-l-2xl border-[0.5px] border-gray-200 shadow-xl bg-white'>
|
||||
<div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-gray-900'>
|
||||
{`Test Run#${historyWorkflowData?.sequence_number}`}
|
||||
</div>
|
||||
<Run runID={historyWorkflowData?.id || ''} />
|
||||
<Run
|
||||
runID={historyWorkflowData?.id || ''}
|
||||
getResultCallback={handleResultCallback}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import OutputPanel from '../run/output-panel'
|
||||
import ResultPanel from '../run/result-panel'
|
||||
import TracingPanel from '../run/tracing-panel'
|
||||
import {
|
||||
useWorkflowRun,
|
||||
useWorkflowInteractions,
|
||||
} from '../hooks'
|
||||
import { useStore } from '../store'
|
||||
import {
|
||||
@ -22,9 +22,10 @@ import { XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
const WorkflowPreview = () => {
|
||||
const { t } = useTranslation()
|
||||
const { handleRunSetting } = useWorkflowRun()
|
||||
const showInputsPanel = useStore(s => s.showInputsPanel)
|
||||
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const showInputsPanel = useStore(s => s.showInputsPanel)
|
||||
const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel)
|
||||
const [currentTab, setCurrentTab] = useState<string>(showInputsPanel ? 'INPUT' : 'TRACING')
|
||||
|
||||
const switchTab = async (tab: string) => {
|
||||
@ -34,6 +35,11 @@ const WorkflowPreview = () => {
|
||||
const [height, setHieght] = useState(0)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (showDebugAndPreviewPanel && showInputsPanel)
|
||||
setCurrentTab('INPUT')
|
||||
}, [showDebugAndPreviewPanel, showInputsPanel])
|
||||
|
||||
const adjustResultHeight = () => {
|
||||
if (ref.current)
|
||||
setHieght(ref.current?.clientHeight - 16 - 16 - 2 - 1)
|
||||
@ -49,11 +55,9 @@ const WorkflowPreview = () => {
|
||||
`}>
|
||||
<div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-gray-900'>
|
||||
{`Test Run${!workflowRunningData?.result.sequence_number ? '' : `#${workflowRunningData?.result.sequence_number}`}`}
|
||||
{showInputsPanel && workflowRunningData?.result?.status !== WorkflowRunningStatus.Running && (
|
||||
<div className='p-1 cursor-pointer' onClick={() => handleRunSetting(true)}>
|
||||
<XClose className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
)}
|
||||
<div className='p-1 cursor-pointer' onClick={() => handleCancelDebugAndPreviewPanel()}>
|
||||
<XClose className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow relative flex flex-col'>
|
||||
<div className='shrink-0 flex items-center px-4 border-b-[0.5px] border-[rgba(0,0,0,0.05)]'>
|
||||
@ -107,7 +111,7 @@ const WorkflowPreview = () => {
|
||||
'grow bg-white h-0 overflow-y-auto rounded-b-2xl',
|
||||
(currentTab === 'RESULT' || currentTab === 'TRACING') && '!bg-gray-50',
|
||||
)}>
|
||||
{currentTab === 'INPUT' && (
|
||||
{currentTab === 'INPUT' && showInputsPanel && (
|
||||
<InputsPanel onRun={() => switchTab('RESULT')} />
|
||||
)}
|
||||
{currentTab === 'RESULT' && (
|
||||
|
@ -23,9 +23,9 @@ type Shape = {
|
||||
appId: string
|
||||
panelWidth: number
|
||||
workflowRunningData?: WorkflowRunningData
|
||||
setWorkflowRunningData: (workflowData: WorkflowRunningData) => void
|
||||
setWorkflowRunningData: (workflowData?: WorkflowRunningData) => void
|
||||
historyWorkflowData?: HistoryWorkflowData
|
||||
setHistoryWorkflowData: (historyWorkflowData: HistoryWorkflowData) => void
|
||||
setHistoryWorkflowData: (historyWorkflowData?: HistoryWorkflowData) => void
|
||||
showRunHistory: boolean
|
||||
setShowRunHistory: (showRunHistory: boolean) => void
|
||||
showFeaturesPanel: boolean
|
||||
@ -68,6 +68,8 @@ type Shape = {
|
||||
setClipboardElements: (clipboardElements: Node[]) => void
|
||||
shortcutsDisabled: boolean
|
||||
setShortcutsDisabled: (shortcutsDisabled: boolean) => void
|
||||
showDebugAndPreviewPanel: boolean
|
||||
setShowDebugAndPreviewPanel: (showDebugAndPreviewPanel: boolean) => void
|
||||
}
|
||||
|
||||
export const createWorkflowStore = () => {
|
||||
@ -117,6 +119,8 @@ export const createWorkflowStore = () => {
|
||||
setClipboardElements: clipboardElements => set(() => ({ clipboardElements })),
|
||||
shortcutsDisabled: false,
|
||||
setShortcutsDisabled: shortcutsDisabled => set(() => ({ shortcutsDisabled })),
|
||||
showDebugAndPreviewPanel: false,
|
||||
setShowDebugAndPreviewPanel: showDebugAndPreviewPanel => set(() => ({ showDebugAndPreviewPanel })),
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type {
|
||||
Edge as ReactFlowEdge,
|
||||
Node as ReactFlowNode,
|
||||
Viewport,
|
||||
} from 'reactflow'
|
||||
import type { TransferMethod } from '@/types/app'
|
||||
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
|
||||
@ -60,6 +61,12 @@ export type NodePanelProps<T> = {
|
||||
}
|
||||
export type Edge = ReactFlowEdge<CommonEdgeType>
|
||||
|
||||
export type WorkflowDataUpdator = {
|
||||
nodes: Node[]
|
||||
edges: Edge[]
|
||||
viewport: Viewport
|
||||
}
|
||||
|
||||
export type ValueSelector = string[] // [nodeId, key | obj key path]
|
||||
|
||||
export type Variable = {
|
||||
|
@ -79,7 +79,9 @@ const getCycleEdges = (nodes: Node[], edges: Edge[]) => {
|
||||
return cycleEdges
|
||||
}
|
||||
|
||||
export const initialNodes = (nodes: Node[], edges: Edge[]) => {
|
||||
export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
|
||||
const nodes = cloneDeep(originNodes)
|
||||
const edges = cloneDeep(originEdges)
|
||||
const firstNode = nodes[0]
|
||||
|
||||
if (!firstNode?.position) {
|
||||
@ -121,7 +123,9 @@ export const initialNodes = (nodes: Node[], edges: Edge[]) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const initialEdges = (edges: Edge[], nodes: Node[]) => {
|
||||
export const initialEdges = (originEdges: Edge[], originNodes: Node[]) => {
|
||||
const nodes = cloneDeep(originNodes)
|
||||
const edges = cloneDeep(originEdges)
|
||||
let selectedNode: Node | null = null
|
||||
const nodesMap = nodes.reduce((acc, node) => {
|
||||
acc[node.id] = node
|
||||
|
@ -49,6 +49,8 @@ const translation = {
|
||||
processData: 'Process Data',
|
||||
input: 'Input',
|
||||
output: 'Output',
|
||||
viewOnly: 'View Only',
|
||||
showRunHistory: 'Show Run History',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}} is required',
|
||||
|
@ -49,6 +49,8 @@ const translation = {
|
||||
processData: '数据处理',
|
||||
input: '输入',
|
||||
output: '输出',
|
||||
viewOnly: '只读',
|
||||
showRunHistory: '显示运行历史',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}} 不能为空',
|
||||
|
Loading…
Reference in New Issue
Block a user