Преглед на файлове

feat: workflow remove preview mode (#3941)

zxhlyh преди 1 година
родител
ревизия
8e4989ed03
променени са 33 файла, в които са добавени 547 реда и са изтрити 307 реда
  1. 5 0
      web/app/components/base/icons/assets/vender/line/communication/message-play.svg
  2. 39 0
      web/app/components/base/icons/src/vender/line/communication/MessagePlay.json
  3. 16 0
      web/app/components/base/icons/src/vender/line/communication/MessagePlay.tsx
  4. 1 0
      web/app/components/base/icons/src/vender/line/communication/index.ts
  5. 14 3
      web/app/components/workflow/header/checklist.tsx
  6. 0 4
      web/app/components/workflow/header/editing-title.tsx
  7. 40 35
      web/app/components/workflow/header/index.tsx
  8. 37 43
      web/app/components/workflow/header/run-and-history.tsx
  9. 11 11
      web/app/components/workflow/header/running-title.tsx
  10. 52 19
      web/app/components/workflow/header/view-history.tsx
  11. 2 0
      web/app/components/workflow/hooks/index.ts
  12. 15 0
      web/app/components/workflow/hooks/use-edges-interactions.ts
  13. 34 8
      web/app/components/workflow/hooks/use-nodes-interactions.ts
  14. 6 2
      web/app/components/workflow/hooks/use-nodes-sync-draft.ts
  15. 50 0
      web/app/components/workflow/hooks/use-workflow-interactions.ts
  16. 14 0
      web/app/components/workflow/hooks/use-workflow-mode.ts
  17. 41 80
      web/app/components/workflow/hooks/use-workflow-run.ts
  18. 2 27
      web/app/components/workflow/hooks/use-workflow.ts
  19. 7 2
      web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx
  20. 20 7
      web/app/components/workflow/nodes/_base/node.tsx
  21. 10 1
      web/app/components/workflow/nodes/_base/panel.tsx
  22. 17 1
      web/app/components/workflow/panel/chat-record/index.tsx
  23. 44 14
      web/app/components/workflow/panel/debug-and-preview/index.tsx
  24. 2 1
      web/app/components/workflow/panel/debug-and-preview/user-input.tsx
  25. 13 31
      web/app/components/workflow/panel/index.tsx
  26. 2 3
      web/app/components/workflow/panel/inputs-panel.tsx
  27. 17 2
      web/app/components/workflow/panel/record.tsx
  28. 13 9
      web/app/components/workflow/panel/workflow-preview.tsx
  29. 6 2
      web/app/components/workflow/store.ts
  30. 7 0
      web/app/components/workflow/types.ts
  31. 6 2
      web/app/components/workflow/utils.ts
  32. 2 0
      web/i18n/en-US/workflow.ts
  33. 2 0
      web/i18n/zh-Hans/workflow.ts

+ 5 - 0
web/app/components/base/icons/assets/vender/line/communication/message-play.svg

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

+ 39 - 0
web/app/components/base/icons/src/vender/line/communication/MessagePlay.json

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

+ 16 - 0
web/app/components/base/icons/src/vender/line/communication/MessagePlay.tsx

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

+ 1 - 0
web/app/components/base/icons/src/vender/line/communication/index.ts

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

+ 14 - 3
web/app/components/workflow/header/checklist.tsx

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

+ 0 - 4
web/app/components/workflow/header/editing-title.tsx

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

+ 40 - 35
web/app/components/workflow/header/index.tsx

@@ -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,
               }}
               }}
             />
             />
-            {
-              !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>
+        )
+      }
+      {
+        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>
           </div>
         )
         )
       }
       }
       {
       {
-        isRestoring && (
+        restoring && (
           <div className='flex items-center'>
           <div className='flex items-center'>
             <Button
             <Button
               className={`
               className={`

+ 37 - 43
web/app/components/workflow/header/run-and-history.tsx

@@ -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({
-        historyWorkflowData: undefined,
-        showInputsPanel: true,
-      })
-      handleSyncWorkflowDraft(true)
+      setShowDebugAndPreviewPanel(true)
+      setShowInputsPanel(true)
     }
     }
   }, [
   }, [
     workflowStore,
     workflowStore,
-    handleSyncWorkflowDraft,
-    handleRunSetting,
     handleRun,
     handleRun,
     doSyncWorkflowDraft,
     doSyncWorkflowDraft,
     store,
     store,
@@ -81,12 +80,11 @@ const RunMode = memo(() => {
   return (
   return (
     <>
     <>
       <div
       <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}
         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 { handleSyncWorkflowDraft } = useNodesSyncDraft()
-  const { nodesReadOnly } = useNodesReadOnly()
+  const workflowStore = useWorkflowStore()
+  const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
 
 
   const handleClick = () => {
   const handleClick = () => {
-    handleSyncWorkflowDraft(true)
-    handleRunSetting()
+    const {
+      showDebugAndPreviewPanel,
+      setShowDebugAndPreviewPanel,
+      setHistoryWorkflowData,
+    } = workflowStore.getState()
+
+    if (showDebugAndPreviewPanel)
+      handleCancelDebugAndPreviewPanel()
+    else
+      setShowDebugAndPreviewPanel(true)
+
+    setHistoryWorkflowData(undefined)
   }
   }
 
 
   return (
   return (
     <div
     <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>
     </div>
   )
   )
 })
 })

+ 11 - 11
web/app/components/workflow/header/running-title.tsx

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

+ 52 - 19
web/app/components/workflow/header/view-history.tsx

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

+ 2 - 0
web/app/components/workflow/hooks/index.ts

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

+ 15 - 0
web/app/components/workflow/hooks/use-edges-interactions.ts

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

+ 34 - 8
web/app/components/workflow/hooks/use-nodes-interactions.ts

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

+ 6 - 2
web/app/components/workflow/hooks/use-nodes-sync-draft.ts

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

+ 50 - 0
web/app/components/workflow/hooks/use-workflow-interactions.ts

@@ -0,0 +1,50 @@
+import { useCallback } from 'react'
+import { useReactFlow } from 'reactflow'
+import { useWorkflowStore } from '../store'
+import { WORKFLOW_DATA_UPDATE } from '../constants'
+import type { WorkflowDataUpdator } from '../types'
+import {
+  initialEdges,
+  initialNodes,
+} from '../utils'
+import { useEdgesInteractions } from './use-edges-interactions'
+import { useNodesInteractions } from './use-nodes-interactions'
+import { useEventEmitterContextContext } from '@/context/event-emitter'
+
+export const useWorkflowInteractions = () => {
+  const reactflow = useReactFlow()
+  const workflowStore = useWorkflowStore()
+  const { handleNodeCancelRunningStatus } = useNodesInteractions()
+  const { handleEdgeCancelRunningStatus } = useEdgesInteractions()
+  const { eventEmitter } = useEventEmitterContextContext()
+
+  const handleCancelDebugAndPreviewPanel = useCallback(() => {
+    workflowStore.setState({
+      showDebugAndPreviewPanel: false,
+    })
+    handleNodeCancelRunningStatus()
+    handleEdgeCancelRunningStatus()
+  }, [workflowStore, handleNodeCancelRunningStatus, handleEdgeCancelRunningStatus])
+
+  const handleUpdateWorkflowCanvas = useCallback((payload: WorkflowDataUpdator) => {
+    const {
+      nodes,
+      edges,
+      viewport,
+    } = payload
+    const { setViewport } = reactflow
+    eventEmitter?.emit({
+      type: WORKFLOW_DATA_UPDATE,
+      payload: {
+        nodes: initialNodes(nodes, edges),
+        edges: initialEdges(edges, nodes),
+      },
+    } as any)
+    setViewport(viewport)
+  }, [eventEmitter, reactflow])
+
+  return {
+    handleCancelDebugAndPreviewPanel,
+    handleUpdateWorkflowCanvas,
+  }
+}

+ 14 - 0
web/app/components/workflow/hooks/use-workflow-mode.ts

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

+ 41 - 80
web/app/components/workflow/hooks/use-workflow-run.ts

@@ -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)
-      setEdges(edges)
-      setViewport(viewport)
+      handleUpdateWorkflowCanvas({
+        nodes,
+        edges,
+        viewport,
+      })
       featuresStore!.setState({ features })
       featuresStore!.setState({ features })
       setBackupDraft(undefined)
       setBackupDraft(undefined)
     }
     }
-  }, [store, reactflow, 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: [],
-        },
-      })
-    }
+  }, [handleUpdateWorkflowCanvas, workflowStore, featuresStore])
 
 
+  const handleRun = useCallback(async (
+    params: any,
+    callback?: IOtherOptions,
+  ) => {
     const {
     const {
-      setNodes,
       getNodes,
       getNodes,
-      edges,
-      setEdges,
+      setNodes,
     } = store.getState()
     } = 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
-        })
+    const newNodes = produce(getNodes(), (draft) => {
+      draft.forEach((node) => {
+        node.data.selected = false
       })
       })
-      setEdges(newEdges)
-    }
-  }, [store, handleLoadBackupDraft, handleBackupDraft, workflowStore])
+    })
+    setNodes(newNodes)
+    await doSyncWorkflowDraft()
 
 
-  const handleRun = useCallback((
-    params: any,
-    callback?: IOtherOptions,
-  ) => {
     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) => {
-      draft.result = {
-        ...draft?.result,
+    setWorkflowRunningData({
+      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,
-            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) => {
           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,

+ 2 - 27
web/app/components/workflow/hooks/use-workflow.ts

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

+ 7 - 2
web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx

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

+ 20 - 7
web/app/components/workflow/nodes/_base/node.tsx

@@ -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'}
-          ${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._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}

+ 10 - 1
web/app/components/workflow/nodes/_base/panel.tsx

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

+ 17 - 1
web/app/components/workflow/panel/chat-record/index.tsx

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

+ 44 - 14
web/app/components/workflow/panel/debug-and-preview/index.tsx

@@ -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()
 
 
-  useKeyPress('shift.r', () => {
+  const handleRestartChat = () => {
+    handleNodeCancelRunningStatus()
+    handleEdgeCancelRunningStatus()
     chatRef.current.handleRestart()
     chatRef.current.handleRestart()
+  }
+
+  useKeyPress('shift.r', () => {
+    handleRestartChat()
   }, {
   }, {
     exactMatch: true,
     exactMatch: true,
   })
   })
 
 
   return (
   return (
     <div
     <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={{
       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
-          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>
       <div className='grow rounded-b-2xl overflow-y-auto'>
       <div className='grow rounded-b-2xl overflow-y-auto'>
         <ChatWrapper ref={chatRef} />
         <ChatWrapper ref={chatRef} />

+ 2 - 1
web/app/components/workflow/panel/debug-and-preview/user-input.tsx

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

+ 13 - 31
web/app/components/workflow/panel/index.tsx

@@ -1,9 +1,7 @@
 import type { FC } from 'react'
 import type { FC } from 'react'
-import {
-  memo,
-  useMemo,
-} from 'react'
+import { memo } 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>
   )
   )
 }
 }

+ 2 - 3
web/app/components/workflow/panel/inputs-panel.tsx

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

+ 17 - 2
web/app/components/workflow/panel/record.tsx

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

+ 13 - 9
web/app/components/workflow/panel/workflow-preview.tsx

@@ -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 showInputsPanel = useStore(s => s.showInputsPanel)
+  const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
   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={() => 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>
       <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' && (

+ 6 - 2
web/app/components/workflow/store.ts

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

+ 7 - 0
web/app/components/workflow/types.ts

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

+ 6 - 2
web/app/components/workflow/utils.ts

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

+ 2 - 0
web/i18n/en-US/workflow.ts

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

+ 2 - 0
web/i18n/zh-Hans/workflow.ts

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