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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 549 additions and 309 deletions

View File

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

View File

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

View File

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

View File

@ -4,3 +4,4 @@ export { default as ChatBot } from './ChatBot'
export { default as CuteRobot } from './CuteRobot' export { default as CuteRobot } from './CuteRobot'
export { default as MessageCheckRemove } from './MessageCheckRemove' export { default as MessageCheckRemove } from './MessageCheckRemove'
export { default as MessageFastPlus } from './MessageFastPlus' export { default as MessageFastPlus } from './MessageFastPlus'
export { default as MessagePlay } from './MessagePlay'

View File

@ -7,6 +7,7 @@ import {
useEdges, useEdges,
useNodes, useNodes,
} from 'reactflow' } from 'reactflow'
import cn from 'classnames'
import BlockIcon from '../block-icon' import BlockIcon from '../block-icon'
import { import {
useChecklist, useChecklist,
@ -28,7 +29,12 @@ import {
} from '@/app/components/base/icons/src/vender/line/general' } from '@/app/components/base/icons/src/vender/line/general'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' 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 { t } = useTranslation()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const nodes = useNodes<CommonNodeType>() const nodes = useNodes<CommonNodeType>()
@ -46,8 +52,13 @@ const WorkflowChecklist = () => {
open={open} open={open}
onOpenChange={setOpen} onOpenChange={setOpen}
> >
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}> <PortalToFollowElemTrigger onClick={() => !disabled && 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'> <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 <div
className={` className={`
group flex items-center justify-center w-full h-full rounded-md cursor-pointer 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 dayjs from 'dayjs'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useWorkflow } from '../hooks' import { useWorkflow } from '../hooks'
import { Edit03 } from '@/app/components/base/icons/src/vender/solid/general'
import { useStore } from '@/app/components/workflow/store' import { useStore } from '@/app/components/workflow/store'
const EditingTitle = () => { const EditingTitle = () => {
@ -13,12 +12,9 @@ const EditingTitle = () => {
return ( return (
<div className='flex items-center h-[18px] text-xs text-gray-500'> <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 && ( !!draftUpdatedAt && (
<> <>
<span className='flex items-center mx-1'>·</span>
{t('workflow.common.autoSaved')} {dayjs(draftUpdatedAt).format('HH:mm:ss')} {t('workflow.common.autoSaved')} {dayjs(draftUpdatedAt).format('HH:mm:ss')}
</> </>
) )

View File

@ -13,6 +13,7 @@ import {
useChecklistBeforePublish, useChecklistBeforePublish,
useNodesReadOnly, useNodesReadOnly,
useNodesSyncDraft, useNodesSyncDraft,
useWorkflowMode,
useWorkflowRun, useWorkflowRun,
} from '../hooks' } from '../hooks'
import AppPublisher from '../../app/app-publisher' import AppPublisher from '../../app/app-publisher'
@ -21,12 +22,13 @@ import RunAndHistory from './run-and-history'
import EditingTitle from './editing-title' import EditingTitle from './editing-title'
import RunningTitle from './running-title' import RunningTitle from './running-title'
import RestoringTitle from './restoring-title' import RestoringTitle from './restoring-title'
import ViewHistory from './view-history'
import Checklist from './checklist' import Checklist from './checklist'
import { Grid01 } from '@/app/components/base/icons/src/vender/line/layout' import { Grid01 } from '@/app/components/base/icons/src/vender/line/layout'
import Button from '@/app/components/base/button' 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 { useStore as useAppStore } from '@/app/components/app/store'
import { publishWorkflow } from '@/service/workflow' import { publishWorkflow } from '@/service/workflow'
import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
const Header: FC = () => { const Header: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
@ -38,18 +40,21 @@ const Header: FC = () => {
nodesReadOnly, nodesReadOnly,
getNodesReadOnly, getNodesReadOnly,
} = useNodesReadOnly() } = useNodesReadOnly()
const isRestoring = useStore(s => s.isRestoring)
const publishedAt = useStore(s => s.publishedAt) const publishedAt = useStore(s => s.publishedAt)
const draftUpdatedAt = useStore(s => s.draftUpdatedAt) const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
const { const {
handleLoadBackupDraft, handleLoadBackupDraft,
handleRunSetting,
handleBackupDraft, handleBackupDraft,
handleRestoreFromPublishedWorkflow, handleRestoreFromPublishedWorkflow,
} = useWorkflowRun() } = useWorkflowRun()
const { handleCheckBeforePublish } = useChecklistBeforePublish() const { handleCheckBeforePublish } = useChecklistBeforePublish()
const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const {
normal,
restoring,
viewHistory,
} = useWorkflowMode()
const handleShowFeatures = useCallback(() => { const handleShowFeatures = useCallback(() => {
const { const {
@ -62,10 +67,6 @@ const Header: FC = () => {
setShowFeaturesPanel(true) setShowFeaturesPanel(true)
}, [workflowStore, getNodesReadOnly]) }, [workflowStore, getNodesReadOnly])
const handleGoBackToEdit = useCallback(() => {
handleRunSetting(true)
}, [handleRunSetting])
const handleCancelRestore = useCallback(() => { const handleCancelRestore = useCallback(() => {
handleLoadBackupDraft() handleLoadBackupDraft()
workflowStore.setState({ isRestoring: false }) workflowStore.setState({ isRestoring: false })
@ -102,6 +103,11 @@ const Header: FC = () => {
handleSyncWorkflowDraft(true) handleSyncWorkflowDraft(true)
}, [handleSyncWorkflowDraft]) }, [handleSyncWorkflowDraft])
const handleGoBackToEdit = useCallback(() => {
handleLoadBackupDraft()
workflowStore.setState({ historyWorkflowData: undefined })
}, [workflowStore, handleLoadBackupDraft])
return ( return (
<div <div
className='absolute top-0 left-0 z-10 flex items-center justify-between w-full px-3 h-14' 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> </div>
{ {
!isRestoring && ( normal && (
<div className='flex items-center'> <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 /> <RunAndHistory />
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div> <div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
<Button <Button
className={` className={`
mr-2 px-3 py-0 h-8 bg-white text-[13px] font-medium text-gray-700 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 border-[0.5px] border-gray-200 shadow-xs
${nodesReadOnly && !isRestoring && 'opacity-50 !cursor-not-allowed'} ${nodesReadOnly && 'opacity-50 !cursor-not-allowed'}
`} `}
onClick={handleShowFeatures} onClick={handleShowFeatures}
> >
@ -166,19 +158,32 @@ const Header: FC = () => {
crossAxisOffset: 53, crossAxisOffset: 53,
}} }}
/> />
{ <div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
!nodesReadOnly && ( <Checklist disabled={nodesReadOnly} />
<>
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
<Checklist />
</>
)
}
</div> </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'> <div className='flex items-center'>
<Button <Button
className={` className={`

View File

@ -2,14 +2,15 @@ import type { FC } from 'react'
import { memo, useCallback } from 'react' import { memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useStoreApi } from 'reactflow' import { useStoreApi } from 'reactflow'
import cn from 'classnames'
import { import {
useStore, useStore,
useWorkflowStore, useWorkflowStore,
} from '../store' } from '../store'
import { import {
useIsChatMode, useIsChatMode,
useNodesReadOnly,
useNodesSyncDraft, useNodesSyncDraft,
useWorkflowInteractions,
useWorkflowRun, useWorkflowRun,
} from '../hooks' } from '../hooks'
import { import {
@ -23,6 +24,7 @@ import {
} from '@/app/components/base/icons/src/vender/line/mediaAndDevices' } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import { Loading02 } from '@/app/components/base/icons/src/vender/line/general' import { Loading02 } from '@/app/components/base/icons/src/vender/line/general'
import { useFeaturesStore } from '@/app/components/base/features/hooks' import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { MessagePlay } from '@/app/components/base/icons/src/vender/line/communication'
const RunMode = memo(() => { const RunMode = memo(() => {
const { t } = useTranslation() const { t } = useTranslation()
@ -31,15 +33,12 @@ const RunMode = memo(() => {
const featuresStore = useFeaturesStore() const featuresStore = useFeaturesStore()
const { const {
handleStopRun, handleStopRun,
handleRunSetting,
handleRun, handleRun,
} = useWorkflowRun() } = useWorkflowRun()
const { const {
doSyncWorkflowDraft, doSyncWorkflowDraft,
handleSyncWorkflowDraft,
} = useNodesSyncDraft() } = useNodesSyncDraft()
const workflowRunningData = useStore(s => s.workflowRunningData) const workflowRunningData = useStore(s => s.workflowRunningData)
const showInputsPanel = useStore(s => s.showInputsPanel)
const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running
const handleClick = useCallback(async () => { const handleClick = useCallback(async () => {
@ -55,23 +54,23 @@ const RunMode = memo(() => {
const startNode = nodes.find(node => node.data.type === BlockEnum.Start) const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
const startVariables = startNode?.data.variables || [] const startVariables = startNode?.data.variables || []
const fileSettings = featuresStore!.getState().features.file const fileSettings = featuresStore!.getState().features.file
const {
setShowDebugAndPreviewPanel,
setShowInputsPanel,
} = workflowStore.getState()
if (!startVariables.length && !fileSettings?.image?.enabled) { if (!startVariables.length && !fileSettings?.image?.enabled) {
await doSyncWorkflowDraft() await doSyncWorkflowDraft()
handleRunSetting()
handleRun({ inputs: {}, files: [] }) handleRun({ inputs: {}, files: [] })
setShowDebugAndPreviewPanel(true)
setShowInputsPanel(false)
} }
else { else {
workflowStore.setState({ setShowDebugAndPreviewPanel(true)
historyWorkflowData: undefined, setShowInputsPanel(true)
showInputsPanel: true,
})
handleSyncWorkflowDraft(true)
} }
}, [ }, [
workflowStore, workflowStore,
handleSyncWorkflowDraft,
handleRunSetting,
handleRun, handleRun,
doSyncWorkflowDraft, doSyncWorkflowDraft,
store, store,
@ -81,12 +80,11 @@ const RunMode = memo(() => {
return ( return (
<> <>
<div <div
className={` className={cn(
flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600 'flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600',
hover:bg-primary-50 cursor-pointer 'hover:bg-primary-50 cursor-pointer',
${showInputsPanel && 'bg-primary-50'} isRunning && 'bg-primary-50 !cursor-not-allowed',
${isRunning && 'bg-primary-50 !cursor-not-allowed'} )}
`}
onClick={handleClick} onClick={handleClick}
> >
{ {
@ -122,38 +120,34 @@ RunMode.displayName = 'RunMode'
const PreviewMode = memo(() => { const PreviewMode = memo(() => {
const { t } = useTranslation() const { t } = useTranslation()
const { handleRunSetting } = useWorkflowRun() const workflowStore = useWorkflowStore()
const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
const { nodesReadOnly } = useNodesReadOnly()
const handleClick = () => { const handleClick = () => {
handleSyncWorkflowDraft(true) const {
handleRunSetting() showDebugAndPreviewPanel,
setShowDebugAndPreviewPanel,
setHistoryWorkflowData,
} = workflowStore.getState()
if (showDebugAndPreviewPanel)
handleCancelDebugAndPreviewPanel()
else
setShowDebugAndPreviewPanel(true)
setHistoryWorkflowData(undefined)
} }
return ( return (
<div <div
className={` className={cn(
flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600 'flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600',
hover:bg-primary-50 cursor-pointer 'hover:bg-primary-50 cursor-pointer',
${nodesReadOnly && 'bg-primary-50 opacity-50 !cursor-not-allowed'} )}
`} onClick={() => handleClick()}
onClick={() => !nodesReadOnly && handleClick()}
> >
{ <MessagePlay className='mr-1 w-4 h-4' />
nodesReadOnly {t('workflow.common.debugAndPreview')}
? (
<>
{t('workflow.common.inPreview')}
</>
)
: (
<>
<Play className='mr-1 w-4 h-4' />
{t('workflow.common.preview')}
</>
)
}
</div> </div>
) )
}) })

View File

@ -1,22 +1,22 @@
import { memo } from 'react' import { memo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store' import { useIsChatMode } from '../hooks'
import { Play } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' import { useStore } from '../store'
import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time'
const RunningTitle = () => { const RunningTitle = () => {
const { t } = useTranslation() const { t } = useTranslation()
const appDetail = useAppStore(state => state.appDetail) const isChatMode = useIsChatMode()
const historyWorkflowData = useStore(s => s.historyWorkflowData)
return ( return (
<div className='flex items-center h-[18px] text-xs text-primary-600'> <div className='flex items-center h-[18px] text-xs text-gray-500'>
<Play className='mr-1 w-3 h-3' /> <ClockPlay className='mr-1 w-3 h-3 text-gray-500' />
{ <span>{isChatMode ? `Test Chat#${historyWorkflowData?.sequence_number}` : `Test Run#${historyWorkflowData?.sequence_number}`}</span>
appDetail?.mode === 'advanced-chat'
? t('workflow.common.inPreviewMode')
: t('workflow.common.inRunMode')
}
<span className='mx-1'>·</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> </div>
) )
} }

View File

@ -8,7 +8,9 @@ import { useTranslation } from 'react-i18next'
import { useShallow } from 'zustand/react/shallow' import { useShallow } from 'zustand/react/shallow'
import { import {
useIsChatMode, useIsChatMode,
useNodesInteractions,
useWorkflow, useWorkflow,
useWorkflowInteractions,
useWorkflowRun, useWorkflowRun,
} from '../hooks' } from '../hooks'
import { WorkflowRunningStatus } from '../types' import { WorkflowRunningStatus } from '../types'
@ -35,11 +37,22 @@ import {
useWorkflowStore, useWorkflowStore,
} from '@/app/components/workflow/store' } from '@/app/components/workflow/store'
const ViewHistory = () => { type ViewHistoryProps = {
withText?: boolean
}
const ViewHistory = ({
withText,
}: ViewHistoryProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const isChatMode = useIsChatMode() const isChatMode = useIsChatMode()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const { formatTimeFromNow } = useWorkflow() const { formatTimeFromNow } = useWorkflow()
const {
handleNodesCancelSelected,
} = useNodesInteractions()
const {
handleCancelDebugAndPreviewPanel,
} = useWorkflowInteractions()
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({ const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({
appDetail: state.appDetail, appDetail: state.appDetail,
@ -57,31 +70,49 @@ const ViewHistory = () => {
return ( return (
( (
<PortalToFollowElem <PortalToFollowElem
placement='bottom-end' placement={withText ? 'bottom-start' : 'bottom-end'}
offset={{ offset={{
mainAxis: 4, mainAxis: 4,
crossAxis: 131, crossAxis: withText ? -8 : 10,
}} }}
open={open} open={open}
onOpenChange={setOpen} onOpenChange={setOpen}
> >
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}> <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
<TooltipPlus {
popupContent={t('workflow.common.viewRunHistory')} withText && (
> <div className={cn(
<div 'flex items-center px-3 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs',
className={` 'text-[13px] font-medium text-primary-600 cursor-pointer',
flex items-center justify-center w-7 h-7 rounded-md hover:bg-black/5 cursor-pointer open && '!bg-primary-50',
${open && 'bg-primary-50'} )}>
`} <ClockPlay
onClick={() => { className={'mr-1 w-4 h-4'}
setCurrentLogItem() />
setShowMessageLogModal(false) {t('workflow.common.showRunHistory')}
}} </div>
> )
<ClockPlay className={`w-4 h-4 ${open ? 'text-primary-600' : 'text-gray-500'}`} /> }
</div> {
</TooltipPlus> !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> </PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[12]'> <PortalToFollowElemContent className='z-[12]'>
<div <div
@ -138,6 +169,8 @@ const ViewHistory = () => {
}) })
handleBackupDraft() handleBackupDraft()
setOpen(false) setOpen(false)
handleNodesCancelSelected()
handleCancelDebugAndPreviewPanel()
}} }}
> >
{ {

View File

@ -7,3 +7,5 @@ export * from './use-workflow'
export * from './use-workflow-run' export * from './use-workflow-run'
export * from './use-workflow-template' export * from './use-workflow-template'
export * from './use-checklist' 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) setEdges(newEdges)
}, [store]) }, [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 { return {
handleEdgeEnter, handleEdgeEnter,
handleEdgeLeave, handleEdgeLeave,
@ -208,5 +222,6 @@ export const useEdgesInteractions = () => {
handleEdgeDelete, handleEdgeDelete,
handleEdgesChange, handleEdgesChange,
handleVariableAssignerEdgesChange, handleVariableAssignerEdgesChange,
handleEdgeCancelRunningStatus,
} }
} }

View File

@ -243,9 +243,6 @@ export const useNodesInteractions = () => {
}, [store, getNodesReadOnly]) }, [store, getNodesReadOnly])
const handleNodeSelect = useCallback((nodeId: string, cancelSelection?: boolean) => { const handleNodeSelect = useCallback((nodeId: string, cancelSelection?: boolean) => {
if (getNodesReadOnly() && !workflowStore.getState().isRestoring)
return
const { const {
getNodes, getNodes,
setNodes, setNodes,
@ -289,14 +286,11 @@ export const useNodesInteractions = () => {
setEdges(newEdges) setEdges(newEdges)
handleSyncWorkflowDraft() handleSyncWorkflowDraft()
}, [store, handleSyncWorkflowDraft, getNodesReadOnly, workflowStore]) }, [store, handleSyncWorkflowDraft])
const handleNodeClick = useCallback<NodeMouseHandler>((_, node) => { const handleNodeClick = useCallback<NodeMouseHandler>((_, node) => {
if (getNodesReadOnly() && !workflowStore.getState().isRestoring)
return
handleNodeSelect(node.id) handleNodeSelect(node.id)
}, [handleNodeSelect, getNodesReadOnly, workflowStore]) }, [handleNodeSelect])
const handleNodeConnect = useCallback<OnConnect>(({ const handleNodeConnect = useCallback<OnConnect>(({
source, source,
@ -834,6 +828,36 @@ export const useNodesInteractions = () => {
handleNodeDelete(node.id) handleNodeDelete(node.id)
}, [getNodesReadOnly, handleNodeDelete, store, workflowStore]) }, [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 { return {
handleNodeDragStart, handleNodeDragStart,
handleNodeDrag, handleNodeDrag,
@ -853,5 +877,7 @@ export const useNodesInteractions = () => {
handleNodeCut, handleNodeCut,
handleNodeDeleteSelected, handleNodeDeleteSelected,
handleNodePaste, handleNodePaste,
handleNodeCancelRunningStatus,
handleNodesCancelSelected,
} }
} }

View File

@ -81,6 +81,8 @@ export const useNodesSyncDraft = () => {
}, [store, featuresStore, workflowStore]) }, [store, featuresStore, workflowStore])
const syncWorkflowDraftWhenPageClose = useCallback(() => { const syncWorkflowDraftWhenPageClose = useCallback(() => {
if (getNodesReadOnly())
return
const postParams = getPostParams() const postParams = getPostParams()
if (postParams) { if (postParams) {
@ -89,16 +91,18 @@ export const useNodesSyncDraft = () => {
JSON.stringify(postParams.params), JSON.stringify(postParams.params),
) )
} }
}, [getPostParams, params.appId]) }, [getPostParams, params.appId, getNodesReadOnly])
const doSyncWorkflowDraft = useCallback(async (appId?: string) => { const doSyncWorkflowDraft = useCallback(async (appId?: string) => {
if (getNodesReadOnly())
return
const postParams = getPostParams(appId) const postParams = getPostParams(appId)
if (postParams) { if (postParams) {
const res = await syncWorkflowDraft(postParams) const res = await syncWorkflowDraft(postParams)
workflowStore.getState().setDraftUpdatedAt(res.updated_at) workflowStore.getState().setDraftUpdatedAt(res.updated_at)
} }
}, [workflowStore, getPostParams]) }, [workflowStore, getPostParams, getNodesReadOnly])
const handleSyncWorkflowDraft = useCallback((sync?: boolean, appId?: string) => { const handleSyncWorkflowDraft = useCallback((sync?: boolean, appId?: string) => {
if (getNodesReadOnly()) 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' } from 'reactflow'
import produce from 'immer' import produce from 'immer'
import { useWorkflowStore } from '../store' import { useWorkflowStore } from '../store'
import { useNodesSyncDraft } from '../hooks'
import { import {
NodeRunningStatus, NodeRunningStatus,
WorkflowRunningStatus, WorkflowRunningStatus,
} from '../types' } from '../types'
import { useWorkflow } from './use-workflow' import { useWorkflowInteractions } from './use-workflow-interactions'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import type { IOtherOptions } from '@/service/base' import type { IOtherOptions } from '@/service/base'
import { ssePost } from '@/service/base' import { ssePost } from '@/service/base'
@ -24,7 +25,8 @@ export const useWorkflowRun = () => {
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
const reactflow = useReactFlow() const reactflow = useReactFlow()
const featuresStore = useFeaturesStore() const featuresStore = useFeaturesStore()
const { renderTreeFromRecord } = useWorkflow() const { doSyncWorkflowDraft } = useNodesSyncDraft()
const { handleUpdateWorkflowCanvas } = useWorkflowInteractions()
const handleBackupDraft = useCallback(() => { const handleBackupDraft = useCallback(() => {
const { const {
@ -45,15 +47,11 @@ export const useWorkflowRun = () => {
viewport: getViewport(), viewport: getViewport(),
features, features,
}) })
doSyncWorkflowDraft()
} }
}, [reactflow, workflowStore, store, featuresStore]) }, [reactflow, workflowStore, store, featuresStore, doSyncWorkflowDraft])
const handleLoadBackupDraft = useCallback(() => { const handleLoadBackupDraft = useCallback(() => {
const {
setNodes,
setEdges,
} = store.getState()
const { setViewport } = reactflow
const { const {
backupDraft, backupDraft,
setBackupDraft, setBackupDraft,
@ -66,64 +64,32 @@ export const useWorkflowRun = () => {
viewport, viewport,
features, features,
} = backupDraft } = backupDraft
setNodes(nodes) handleUpdateWorkflowCanvas({
setEdges(edges) nodes,
setViewport(viewport) edges,
viewport,
})
featuresStore!.setState({ features }) featuresStore!.setState({ features })
setBackupDraft(undefined) setBackupDraft(undefined)
} }
}, [store, reactflow, workflowStore, featuresStore]) }, [handleUpdateWorkflowCanvas, workflowStore, featuresStore])
const handleRunSetting = useCallback((shouldClear?: boolean) => { const handleRun = useCallback(async (
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((
params: any, params: any,
callback?: IOtherOptions, callback?: IOtherOptions,
) => { ) => {
const {
getNodes,
setNodes,
} = store.getState()
const newNodes = produce(getNodes(), (draft) => {
draft.forEach((node) => {
node.data.selected = false
})
})
setNodes(newNodes)
await doSyncWorkflowDraft()
const { const {
onWorkflowStarted, onWorkflowStarted,
onWorkflowFinished, onWorkflowFinished,
@ -151,15 +117,14 @@ export const useWorkflowRun = () => {
let prevNodeId = '' let prevNodeId = ''
const { const {
workflowRunningData,
setWorkflowRunningData, setWorkflowRunningData,
} = workflowStore.getState() } = workflowStore.getState()
setWorkflowRunningData(produce(workflowRunningData!, (draft) => { setWorkflowRunningData({
draft.result = { result: {
...draft?.result,
status: WorkflowRunningStatus.Running, status: WorkflowRunningStatus.Running,
} },
})) tracing: [],
})
ssePost( ssePost(
url, url,
@ -174,8 +139,6 @@ export const useWorkflowRun = () => {
setWorkflowRunningData, setWorkflowRunningData,
} = workflowStore.getState() } = workflowStore.getState()
const { const {
getNodes,
setNodes,
edges, edges,
setEdges, setEdges,
} = store.getState() } = 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) => { const newEdges = produce(edges, (draft) => {
draft.forEach((edge) => { draft.forEach((edge) => {
edge.data = { edge.data = {
@ -253,6 +210,7 @@ export const useWorkflowRun = () => {
setNodes, setNodes,
edges, edges,
setEdges, setEdges,
transform,
} = store.getState() } = store.getState()
const nodes = getNodes() const nodes = getNodes()
setWorkflowRunningData(produce(workflowRunningData!, (draft) => { setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
@ -268,12 +226,12 @@ export const useWorkflowRun = () => {
const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id) const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id)
const currentNode = nodes[currentNodeIndex] const currentNode = nodes[currentNodeIndex]
const position = currentNode.position const position = currentNode.position
const zoom = 1 const zoom = transform[2]
setViewport({ setViewport({
x: (clientWidth - 400 - currentNode.width!) / 2 - position.x, x: (clientWidth - 400 - currentNode.width! * zoom) / 2 - position.x * zoom,
y: (clientHeight - currentNode.height!) / 2 - position.y, y: (clientHeight - currentNode.height! * zoom) / 2 - position.y * zoom,
zoom, zoom: transform[2],
}) })
const newNodes = produce(nodes, (draft) => { const newNodes = produce(nodes, (draft) => {
draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running
@ -329,7 +287,7 @@ export const useWorkflowRun = () => {
...restCallback, ...restCallback,
}, },
) )
}, [store, reactflow, workflowStore]) }, [store, reactflow, workflowStore, doSyncWorkflowDraft])
const handleStopRun = useCallback((taskId: string) => { const handleStopRun = useCallback((taskId: string) => {
const appId = useAppStore.getState().appDetail?.id const appId = useAppStore.getState().appDetail?.id
@ -344,18 +302,21 @@ export const useWorkflowRun = () => {
if (publishedWorkflow) { if (publishedWorkflow) {
const nodes = publishedWorkflow.graph.nodes const nodes = publishedWorkflow.graph.nodes
const edges = publishedWorkflow.graph.edges 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 }) featuresStore?.setState({ features: publishedWorkflow.features })
workflowStore.getState().setPublishedAt(publishedWorkflow.created_at) workflowStore.getState().setPublishedAt(publishedWorkflow.created_at)
} }
}, [featuresStore, workflowStore, renderTreeFromRecord]) }, [featuresStore, handleUpdateWorkflowCanvas, workflowStore])
return { return {
handleBackupDraft, handleBackupDraft,
handleLoadBackupDraft, handleLoadBackupDraft,
handleRunSetting,
handleRun, handleRun,
handleStopRun, handleStopRun,
handleRestoreFromPublishedWorkflow, handleRestoreFromPublishedWorkflow,

View File

@ -16,15 +16,11 @@ import {
} from 'reactflow' } from 'reactflow'
import type { import type {
Connection, Connection,
Viewport,
} from 'reactflow' } from 'reactflow'
import { import {
getLayoutByDagre, getLayoutByDagre,
initialEdges,
initialNodes,
} from '../utils' } from '../utils'
import type { import type {
Edge,
Node, Node,
ValueSelector, ValueSelector,
} from '../types' } from '../types'
@ -39,7 +35,6 @@ import {
import { import {
AUTO_LAYOUT_OFFSET, AUTO_LAYOUT_OFFSET,
SUPPORT_OUTPUT_VARS_NODE, SUPPORT_OUTPUT_VARS_NODE,
WORKFLOW_DATA_UPDATE,
} from '../constants' } from '../constants'
import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils' import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
import { useNodesExtraData } from './use-nodes-data' import { useNodesExtraData } from './use-nodes-data'
@ -58,7 +53,6 @@ import {
fetchAllCustomTools, fetchAllCustomTools,
} from '@/service/tools' } from '@/service/tools'
import I18n from '@/context/i18n' import I18n from '@/context/i18n'
import { useEventEmitterContextContext } from '@/context/event-emitter'
export const useIsChatMode = () => { export const useIsChatMode = () => {
const appDetail = useAppStore(s => s.appDetail) const appDetail = useAppStore(s => s.appDetail)
@ -73,7 +67,6 @@ export const useWorkflow = () => {
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
const nodesExtraData = useNodesExtraData() const nodesExtraData = useNodesExtraData()
const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const { eventEmitter } = useEventEmitterContextContext()
const setPanelWidth = useCallback((width: number) => { const setPanelWidth = useCallback((width: number) => {
localStorage.setItem('workflow-node-panel-width', `${width}`) 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() return dayjs(time).locale(locale === 'zh-Hans' ? 'zh-cn' : locale).fromNow()
}, [locale]) }, [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 getNode = useCallback((nodeId?: string) => {
const { getNodes } = store.getState() const { getNodes } = store.getState()
const nodes = getNodes() const nodes = getNodes()
@ -369,7 +345,6 @@ export const useWorkflow = () => {
isNodeVarsUsedInNodes, isNodeVarsUsedInNodes,
isValidConnection, isValidConnection,
formatTimeFromNow, formatTimeFromNow,
renderTreeFromRecord,
getNode, getNode,
getBeforeNodeById, getBeforeNodeById,
enableShortcuts, enableShortcuts,
@ -510,11 +485,11 @@ export const useNodesReadOnly = () => {
isRestoring, isRestoring,
} = workflowStore.getState() } = workflowStore.getState()
return workflowRunningData || historyWorkflowData || isRestoring return workflowRunningData?.result.status === WorkflowRunningStatus.Running || historyWorkflowData || isRestoring
}, [workflowStore]) }, [workflowStore])
return { return {
nodesReadOnly: !!(workflowRunningData || historyWorkflowData || isRestoring), nodesReadOnly: !!(workflowRunningData?.result.status === WorkflowRunningStatus.Running || historyWorkflowData || isRestoring),
getNodesReadOnly, getNodesReadOnly,
} }
} }

View File

@ -21,6 +21,7 @@ type Props = {
value: any value: any
onChange: (value: any) => void onChange: (value: any) => void
className?: string className?: string
autoFocus?: boolean
} }
const FormItem: FC<Props> = ({ const FormItem: FC<Props> = ({
@ -28,6 +29,7 @@ const FormItem: FC<Props> = ({
value, value,
onChange, onChange,
className, className,
autoFocus,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { type } = payload const { type } = payload
@ -87,6 +89,7 @@ const FormItem: FC<Props> = ({
value={value || ''} value={value || ''}
onChange={e => onChange(e.target.value)} onChange={e => onChange(e.target.value)}
placeholder={t('appDebug.variableConig.inputPlaceholder')!} placeholder={t('appDebug.variableConig.inputPlaceholder')!}
autoFocus={autoFocus}
/> />
) )
} }
@ -99,6 +102,7 @@ const FormItem: FC<Props> = ({
value={value || ''} value={value || ''}
onChange={e => onChange(e.target.value)} onChange={e => onChange(e.target.value)}
placeholder={t('appDebug.variableConig.inputPlaceholder')!} placeholder={t('appDebug.variableConig.inputPlaceholder')!}
autoFocus={autoFocus}
/> />
) )
} }
@ -110,6 +114,7 @@ const FormItem: FC<Props> = ({
value={value || ''} value={value || ''}
onChange={e => onChange(e.target.value)} onChange={e => onChange(e.target.value)}
placeholder={t('appDebug.variableConig.inputPlaceholder')!} placeholder={t('appDebug.variableConig.inputPlaceholder')!}
autoFocus={autoFocus}
/> />
) )
} }
@ -141,9 +146,9 @@ const FormItem: FC<Props> = ({
type === InputVarType.files && ( type === InputVarType.files && (
<TextGenerationImageUploader <TextGenerationImageUploader
settings={{ settings={{
...fileSettings.image, ...fileSettings?.image,
detail: Resolution.high, detail: Resolution.high,
}} } as any}
onFilesChange={files => onChange(files.filter(file => file.progress !== -1).map(fileItem => ({ onFilesChange={files => onChange(files.filter(file => file.progress !== -1).map(fileItem => ({
type: 'image', type: 'image',
transfer_method: fileItem.type, transfer_method: fileItem.type,

View File

@ -5,6 +5,7 @@ import type {
import { import {
cloneElement, cloneElement,
memo, memo,
useMemo,
} from 'react' } from 'react'
import type { NodeProps } from '../../types' import type { NodeProps } from '../../types'
import { import {
@ -38,11 +39,24 @@ const BaseNode: FC<BaseNodeProps> = ({
}) => { }) => {
const { nodesReadOnly } = useNodesReadOnly() const { nodesReadOnly } = useNodesReadOnly()
const toolIcon = useToolIcon(data) 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 ( return (
<div <div
className={` className={`
flex border-[2px] rounded-2xl 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 <div
@ -50,15 +64,14 @@ const BaseNode: FC<BaseNodeProps> = ({
group relative pb-1 w-[240px] bg-[#fcfdff] shadow-xs group relative pb-1 w-[240px] bg-[#fcfdff] shadow-xs
border border-transparent rounded-[15px] border border-transparent rounded-[15px]
${!data._runningStatus && 'hover:shadow-lg'} ${!data._runningStatus && 'hover:shadow-lg'}
${data._runningStatus === NodeRunningStatus.Running && '!border-primary-500'} ${showRunningBorder && '!border-primary-500'}
${data._runningStatus === NodeRunningStatus.Succeeded && '!border-[#12B76A]'} ${showSuccessBorder && '!border-[#12B76A]'}
${data._runningStatus === NodeRunningStatus.Failed && '!border-[#F04438]'} ${showFailedBorder && '!border-[#F04438]'}
${data._runningStatus === NodeRunningStatus.Waiting && 'opacity-70'}
${data._isInvalidConnection && '!border-[#F04438]'} ${data._isInvalidConnection && '!border-[#F04438]'}
`} `}
> >
{ {
data.type !== BlockEnum.VariableAssigner && !data._runningStatus && ( data.type !== BlockEnum.VariableAssigner && (
<NodeTargetHandle <NodeTargetHandle
id={id} id={id}
data={data} 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 <NodeSourceHandle
id={id} id={id}
data={data} data={data}

View File

@ -7,6 +7,8 @@ import {
memo, memo,
useCallback, useCallback,
} from 'react' } from 'react'
import cn from 'classnames'
import { useShallow } from 'zustand/react/shallow'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import NextStep from './components/next-step' import NextStep from './components/next-step'
import PanelOperator from './components/panel-operator' 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 { Play } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import TooltipPlus from '@/app/components/base/tooltip-plus' import TooltipPlus from '@/app/components/base/tooltip-plus'
import type { Node } from '@/app/components/workflow/types' import type { Node } from '@/app/components/workflow/types'
import { useStore as useAppStore } from '@/app/components/app/store'
type BasePanelProps = { type BasePanelProps = {
children: ReactElement children: ReactElement
@ -43,6 +46,9 @@ const BasePanel: FC<BasePanelProps> = ({
children, children,
}) => { }) => {
const { t } = useTranslation() 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 panelWidth = localStorage.getItem('workflow-node-panel-width') ? parseFloat(localStorage.getItem('workflow-node-panel-width')!) : 420
const { const {
setPanelWidth, setPanelWidth,
@ -82,7 +88,10 @@ const BasePanel: FC<BasePanelProps> = ({
}, [handleNodeDataUpdateWithSyncDraft, id]) }, [handleNodeDataUpdateWithSyncDraft, id])
return ( 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 <div
ref={triggerRef} ref={triggerRef}
className='absolute top-1/2 -translate-y-1/2 -left-2 w-3 h-6 cursor-col-resize resize-x'> 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, useMemo,
useState, useState,
} from 'react' } from 'react'
import { useStore } from '../../store' import {
useStore,
useWorkflowStore,
} from '../../store'
import { useWorkflowRun } from '../../hooks'
import UserInput from './user-input' import UserInput from './user-input'
import Chat from '@/app/components/base/chat/chat' import Chat from '@/app/components/base/chat/chat'
import type { ChatItem } from '@/app/components/base/chat/types' import type { ChatItem } from '@/app/components/base/chat/types'
import { fetchConvesationMessages } from '@/service/debug' import { fetchConvesationMessages } from '@/service/debug'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
const ChatRecord = () => { const ChatRecord = () => {
const [fetched, setFetched] = useState(false) const [fetched, setFetched] = useState(false)
const [chatList, setChatList] = useState([]) const [chatList, setChatList] = useState([])
const appDetail = useAppStore(s => s.appDetail) const appDetail = useAppStore(s => s.appDetail)
const workflowStore = useWorkflowStore()
const { handleLoadBackupDraft } = useWorkflowRun()
const historyWorkflowData = useStore(s => s.historyWorkflowData) const historyWorkflowData = useStore(s => s.historyWorkflowData)
const currentConversationID = historyWorkflowData?.conversation_id 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'> <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}`} {`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>
<div className='grow h-0'> <div className='grow h-0'>
<Chat <Chat

View File

@ -3,10 +3,17 @@ import {
useRef, useRef,
} from 'react' } from 'react'
import { useKeyPress } from 'ahooks' import { useKeyPress } from 'ahooks'
import cn from 'classnames'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import {
useEdgesInteractions,
useNodesInteractions,
useWorkflowInteractions,
} from '../../hooks'
import ChatWrapper from './chat-wrapper' import ChatWrapper from './chat-wrapper'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows' 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 = { export type ChatWrapperRefType = {
handleRestart: () => void handleRestart: () => void
@ -14,33 +21,56 @@ export type ChatWrapperRefType = {
const DebugAndPreview = () => { const DebugAndPreview = () => {
const { t } = useTranslation() const { t } = useTranslation()
const chatRef = useRef({ handleRestart: () => {} }) const chatRef = useRef({ handleRestart: () => {} })
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
const { handleNodeCancelRunningStatus } = useNodesInteractions()
const { handleEdgeCancelRunningStatus } = useEdgesInteractions()
const handleRestartChat = () => {
handleNodeCancelRunningStatus()
handleEdgeCancelRunningStatus()
chatRef.current.handleRestart()
}
useKeyPress('shift.r', () => { useKeyPress('shift.r', () => {
chatRef.current.handleRestart() handleRestartChat()
}, { }, {
exactMatch: true, exactMatch: true,
}) })
return ( return (
<div <div
className={` className={cn(
flex flex-col w-[400px] rounded-l-2xl h-full border border-black/[0.02] shadow-xl 'flex flex-col w-[400px] rounded-l-2xl h-full border border-black/[0.02]',
`} )}
style={{ style={{
background: 'linear-gradient(156deg, rgba(242, 244, 247, 0.80) 0%, rgba(242, 244, 247, 0.00) 99.43%), var(--white, #FFF)', 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()} {t('workflow.common.debugAndPreview').toLocaleUpperCase()}
<Button <div className='flex items-center'>
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' <Button
onClick={() => chatRef.current.handleRestart()} 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='mr-1 w-3.5 h-3.5' /> >
{t('common.operation.refresh')} <RefreshCcw01 className='shrink-0 mr-1 w-3 h-3 text-gray-500' />
<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
<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> className='grow truncate uppercase'
</Button> 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>
<div className='grow rounded-b-2xl overflow-y-auto'> <div className='grow rounded-b-2xl overflow-y-auto'>
<ChatWrapper ref={chatRef} /> <ChatWrapper ref={chatRef} />

View File

@ -56,12 +56,13 @@ const UserInput = () => {
expanded && ( expanded && (
<div className='py-2 text-[13px] text-gray-900'> <div className='py-2 text-[13px] text-gray-900'>
{ {
variables.map(variable => ( variables.map((variable, index) => (
<div <div
key={variable.variable} key={variable.variable}
className='mb-2 last-of-type:mb-0' className='mb-2 last-of-type:mb-0'
> >
<FormItem <FormItem
autoFocus={index === 0}
payload={variable} payload={variable}
value={inputs[variable.variable]} value={inputs[variable.variable]}
onChange={v => handleValueChange(variable.variable, v)} onChange={v => handleValueChange(variable.variable, v)}

View File

@ -1,9 +1,7 @@
import type { FC } from 'react' import type { FC } from 'react'
import { import { memo } from 'react'
memo,
useMemo,
} from 'react'
import { useNodes } from 'reactflow' import { useNodes } from 'reactflow'
import cn from 'classnames'
import { useShallow } from 'zustand/react/shallow' import { useShallow } from 'zustand/react/shallow'
import type { CommonNodeType } from '../types' import type { CommonNodeType } from '../types'
import { Panel as NodePanel } from '../nodes' import { Panel as NodePanel } from '../nodes'
@ -23,9 +21,8 @@ const Panel: FC = () => {
const nodes = useNodes<CommonNodeType>() const nodes = useNodes<CommonNodeType>()
const isChatMode = useIsChatMode() const isChatMode = useIsChatMode()
const selectedNode = nodes.find(node => node.data.selected) 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 historyWorkflowData = useStore(s => s.historyWorkflowData)
const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel)
const isRestoring = useStore(s => s.isRestoring) const isRestoring = useStore(s => s.isRestoring)
const { const {
enableShortcuts, enableShortcuts,
@ -37,28 +34,13 @@ const Panel: FC = () => {
showMessageLogModal: state.showMessageLogModal, showMessageLogModal: state.showMessageLogModal,
setShowMessageLogModal: state.setShowMessageLogModal, 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 ( return (
<div <div
tabIndex={-1} 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} onFocus={disableShortcuts}
onBlur={enableShortcuts} onBlur={enableShortcuts}
key={`${isRestoring}`} key={`${isRestoring}`}
@ -76,6 +58,11 @@ const Panel: FC = () => {
/> />
) )
} }
{
!!selectedNode && (
<NodePanel {...selectedNode!} />
)
}
{ {
historyWorkflowData && !isChatMode && ( historyWorkflowData && !isChatMode && (
<Record /> <Record />
@ -87,20 +74,15 @@ const Panel: FC = () => {
) )
} }
{ {
showDebugAndPreviewPanel && ( showDebugAndPreviewPanel && isChatMode && (
<DebugAndPreview /> <DebugAndPreview />
) )
} }
{ {
showWorkflowPreview && ( showDebugAndPreviewPanel && !isChatMode && (
<WorkflowPreview /> <WorkflowPreview />
) )
} }
{
showNodePanel && (
<NodePanel {...selectedNode!} />
)
}
</div> </div>
) )
} }

View File

@ -34,7 +34,6 @@ const InputsPanel = ({ onRun }: Props) => {
const workflowRunningData = useStore(s => s.workflowRunningData) const workflowRunningData = useStore(s => s.workflowRunningData)
const { const {
handleRun, handleRun,
handleRunSetting,
} = useWorkflowRun() } = useWorkflowRun()
const startNode = nodes.find(node => node.data.type === BlockEnum.Start) const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
const startVariables = startNode?.data.variables const startVariables = startNode?.data.variables
@ -72,7 +71,6 @@ const InputsPanel = ({ onRun }: Props) => {
const doRun = () => { const doRun = () => {
onRun() onRun()
handleRunSetting()
handleRun({ inputs, files }) handleRun({ inputs, files })
} }
@ -87,12 +85,13 @@ const InputsPanel = ({ onRun }: Props) => {
<> <>
<div className='px-4 pb-2'> <div className='px-4 pb-2'>
{ {
variables.map(variable => ( variables.map((variable, index) => (
<div <div
key={variable.variable} key={variable.variable}
className='mb-2 last-of-type:mb-0' className='mb-2 last-of-type:mb-0'
> >
<FormItem <FormItem
autoFocus={index === 0}
className='!block' className='!block'
payload={variable} payload={variable}
value={inputs[variable.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 Run from '../run'
import { useStore } from '../store' import { useStore } from '../store'
import { useWorkflowInteractions } from '../hooks'
const Record = () => { const Record = () => {
const historyWorkflowData = useStore(s => s.historyWorkflowData) 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 ( 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 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'> <div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-gray-900'>
{`Test Run#${historyWorkflowData?.sequence_number}`} {`Test Run#${historyWorkflowData?.sequence_number}`}
</div> </div>
<Run runID={historyWorkflowData?.id || ''} /> <Run
runID={historyWorkflowData?.id || ''}
getResultCallback={handleResultCallback}
/>
</div> </div>
) )
} }

View File

@ -10,7 +10,7 @@ import OutputPanel from '../run/output-panel'
import ResultPanel from '../run/result-panel' import ResultPanel from '../run/result-panel'
import TracingPanel from '../run/tracing-panel' import TracingPanel from '../run/tracing-panel'
import { import {
useWorkflowRun, useWorkflowInteractions,
} from '../hooks' } from '../hooks'
import { useStore } from '../store' import { useStore } from '../store'
import { import {
@ -22,9 +22,10 @@ import { XClose } from '@/app/components/base/icons/src/vender/line/general'
const WorkflowPreview = () => { const WorkflowPreview = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { handleRunSetting } = useWorkflowRun() const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
const showInputsPanel = useStore(s => s.showInputsPanel)
const workflowRunningData = useStore(s => s.workflowRunningData) 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 [currentTab, setCurrentTab] = useState<string>(showInputsPanel ? 'INPUT' : 'TRACING')
const switchTab = async (tab: string) => { const switchTab = async (tab: string) => {
@ -34,6 +35,11 @@ const WorkflowPreview = () => {
const [height, setHieght] = useState(0) const [height, setHieght] = useState(0)
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
if (showDebugAndPreviewPanel && showInputsPanel)
setCurrentTab('INPUT')
}, [showDebugAndPreviewPanel, showInputsPanel])
const adjustResultHeight = () => { const adjustResultHeight = () => {
if (ref.current) if (ref.current)
setHieght(ref.current?.clientHeight - 16 - 16 - 2 - 1) 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'> <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}`}`} {`Test Run${!workflowRunningData?.result.sequence_number ? '' : `#${workflowRunningData?.result.sequence_number}`}`}
{showInputsPanel && workflowRunningData?.result?.status !== WorkflowRunningStatus.Running && ( <div className='p-1 cursor-pointer' onClick={() => handleCancelDebugAndPreviewPanel()}>
<div className='p-1 cursor-pointer' onClick={() => handleRunSetting(true)}> <XClose className='w-4 h-4 text-gray-500' />
<XClose className='w-4 h-4 text-gray-500' /> </div>
</div>
)}
</div> </div>
<div className='grow relative flex flex-col'> <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)]'> <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', 'grow bg-white h-0 overflow-y-auto rounded-b-2xl',
(currentTab === 'RESULT' || currentTab === 'TRACING') && '!bg-gray-50', (currentTab === 'RESULT' || currentTab === 'TRACING') && '!bg-gray-50',
)}> )}>
{currentTab === 'INPUT' && ( {currentTab === 'INPUT' && showInputsPanel && (
<InputsPanel onRun={() => switchTab('RESULT')} /> <InputsPanel onRun={() => switchTab('RESULT')} />
)} )}
{currentTab === 'RESULT' && ( {currentTab === 'RESULT' && (

View File

@ -23,9 +23,9 @@ type Shape = {
appId: string appId: string
panelWidth: number panelWidth: number
workflowRunningData?: WorkflowRunningData workflowRunningData?: WorkflowRunningData
setWorkflowRunningData: (workflowData: WorkflowRunningData) => void setWorkflowRunningData: (workflowData?: WorkflowRunningData) => void
historyWorkflowData?: HistoryWorkflowData historyWorkflowData?: HistoryWorkflowData
setHistoryWorkflowData: (historyWorkflowData: HistoryWorkflowData) => void setHistoryWorkflowData: (historyWorkflowData?: HistoryWorkflowData) => void
showRunHistory: boolean showRunHistory: boolean
setShowRunHistory: (showRunHistory: boolean) => void setShowRunHistory: (showRunHistory: boolean) => void
showFeaturesPanel: boolean showFeaturesPanel: boolean
@ -68,6 +68,8 @@ type Shape = {
setClipboardElements: (clipboardElements: Node[]) => void setClipboardElements: (clipboardElements: Node[]) => void
shortcutsDisabled: boolean shortcutsDisabled: boolean
setShortcutsDisabled: (shortcutsDisabled: boolean) => void setShortcutsDisabled: (shortcutsDisabled: boolean) => void
showDebugAndPreviewPanel: boolean
setShowDebugAndPreviewPanel: (showDebugAndPreviewPanel: boolean) => void
} }
export const createWorkflowStore = () => { export const createWorkflowStore = () => {
@ -117,6 +119,8 @@ export const createWorkflowStore = () => {
setClipboardElements: clipboardElements => set(() => ({ clipboardElements })), setClipboardElements: clipboardElements => set(() => ({ clipboardElements })),
shortcutsDisabled: false, shortcutsDisabled: false,
setShortcutsDisabled: shortcutsDisabled => set(() => ({ shortcutsDisabled })), setShortcutsDisabled: shortcutsDisabled => set(() => ({ shortcutsDisabled })),
showDebugAndPreviewPanel: false,
setShowDebugAndPreviewPanel: showDebugAndPreviewPanel => set(() => ({ showDebugAndPreviewPanel })),
})) }))
} }

View File

@ -1,6 +1,7 @@
import type { import type {
Edge as ReactFlowEdge, Edge as ReactFlowEdge,
Node as ReactFlowNode, Node as ReactFlowNode,
Viewport,
} from 'reactflow' } from 'reactflow'
import type { TransferMethod } from '@/types/app' import type { TransferMethod } from '@/types/app'
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types' 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 Edge = ReactFlowEdge<CommonEdgeType>
export type WorkflowDataUpdator = {
nodes: Node[]
edges: Edge[]
viewport: Viewport
}
export type ValueSelector = string[] // [nodeId, key | obj key path] export type ValueSelector = string[] // [nodeId, key | obj key path]
export type Variable = { export type Variable = {

View File

@ -79,7 +79,9 @@ const getCycleEdges = (nodes: Node[], edges: Edge[]) => {
return cycleEdges 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] const firstNode = nodes[0]
if (!firstNode?.position) { 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 let selectedNode: Node | null = null
const nodesMap = nodes.reduce((acc, node) => { const nodesMap = nodes.reduce((acc, node) => {
acc[node.id] = node acc[node.id] = node

View File

@ -49,6 +49,8 @@ const translation = {
processData: 'Process Data', processData: 'Process Data',
input: 'Input', input: 'Input',
output: 'Output', output: 'Output',
viewOnly: 'View Only',
showRunHistory: 'Show Run History',
}, },
errorMsg: { errorMsg: {
fieldRequired: '{{field}} is required', fieldRequired: '{{field}} is required',

View File

@ -49,6 +49,8 @@ const translation = {
processData: '数据处理', processData: '数据处理',
input: '输入', input: '输入',
output: '输出', output: '输出',
viewOnly: '只读',
showRunHistory: '显示运行历史',
}, },
errorMsg: { errorMsg: {
fieldRequired: '{{field}} 不能为空', fieldRequired: '{{field}} 不能为空',