feat: workflow remove preview mode (#3941)

This commit is contained in:
zxhlyh
2024-04-28 17:09:56 +08:00
committed by GitHub
parent 0940f01634
commit 8e4989ed03
33 changed files with 549 additions and 309 deletions

View File

@@ -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

View File

@@ -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')}
</>
)

View File

@@ -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={`

View File

@@ -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>
)
})

View File

@@ -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>
)
}

View File

@@ -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()
}}
>
{

View File

@@ -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'

View File

@@ -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,
}
}

View File

@@ -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,
}
}

View File

@@ -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())

View File

@@ -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,
}
}

View 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])
}

View File

@@ -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,

View File

@@ -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,
}
}

View File

@@ -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,

View File

@@ -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}

View File

@@ -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'>

View File

@@ -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

View File

@@ -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} />

View File

@@ -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)}

View File

@@ -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>
)
}

View File

@@ -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]}

View File

@@ -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>
)
}

View File

@@ -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' && (

View File

@@ -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 })),
}))
}

View File

@@ -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 = {

View File

@@ -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